Dr. Dobb's is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.


Channels ▼
RSS

JVM Languages

Mobile Java & 3D Applications


September, 2005: Mobile Java & 3D Apps

Oscar and Tom are support engineers for Sony Ericsson Developer World, http://www.SonyEricsson.com/developer/.


The mobile phone has changed tremendously over the past decade. While pundits may talk about technology convergence coming to the living room, it has already arrived on your mobile phone. This convergence is still a work in progress. It has arrived to the point where the mobile phone is capable of functioning not only as a true imaging and communication device, but also as an advanced gaming device and a deliverer of 3D content. To accomplish the latter, mobile phone manufacturer Sony Ericsson has incorporated not one, but two APIs that manage the real-time rendering of 3D graphics on a mobile phone. In this article, we examine these 3D APIs and show how to use a few of their capabilities.

The two 3D APIs on Sony Ericsson mobile phones are HI Corporation's Mascot Capsule Micro3D Version 3, and Java Specification Request (JSR) 184, which describes the Mobile 3D Graphics (M3G) API for the Java 2 Mobile Edition (J2ME). Both APIs support high-quality model rendering, using transparent color blending and appearance properties, plus applying texture maps to the model's surfaces. They also let you import files that store a model's geometry data, its appearance properties, texture maps, and animation sequences. This allows the hard work of assembling the model and any animation sequences to be done on a PC using 3D-authoring software such as Newtek's Lightwave, Discreet's Studio 3D Max, Avid System's SoftImage, and Alias System's Maya. The work is then exported via a plug-in for use in a mobile Java application. This eliminates the computationally expensive stage of generating a model's geometry through algorithms (although you can do that if necessary), and instead focuses the mobile phone's processing power on the rendering stage.

Why have two disparate APIs in a mobile phone? Sony Ericsson's goal is to encourage widespread adoption of 3D applications and 3D-application development. Mascot Capsule V3 had already been field tested and offers opportunities to migrate 3D applications from existing BREW CDMA or DoJa devices to the J2ME platform on GSM/UMTS phones. It also lets you deliver 3D content now while JSR 184 tools and devices become widely available, and the standard matures.

Each of these APIs takes a different approach to solving the same problem of rendering 3D models quickly while consuming few platform resources.

Mascot Capsule Micro3D Version 3

Mascot Capsule V3 started life as a 3D-rendering engine for embedded devices. Such an environment requires robust code and has a small resource footprint—precisely what the J2ME platform requires (http://www.hicorp.co.jp/english/products/mc_top.html). Although Mascot Capsule V3 has a proprietary API, its wide adoption by mobile phone vendors such as Sony Ericsson, Motorola, and Sanyo make it a de facto standard.

Mascot Capsule V3 consists of an enhanced rendering engine and Java extension that exposes its proprietary APIs for use on the J2ME platform. The rendering engine uses 32-bit integer arithmetic operations exclusively; thus, rendering functions don't require any graphic acceleration hardware or a floating-point math coprocessor. A side effect of this is that 3D programmers must pass all coordinate data and API arguments to Mascot Capsule V3 as integers within the range of 0 (equivalent to 0.0) and 4096 (equivalent to 1.0). This can be unsettling for seasoned 3D programmers who are used to supplying floating-point values to 3D APIs such as OpenGL.

As Figure 1 shows, Mascot Capsule V3 hews to its embedded roots with a minimalist set of 10 classes that manage the display and control of 3D content. However, don't be fooled by Mascot Capsule V3's lean architecture: The methods provided by these classes are feature rich and handle the most commonly used 3D-graphics operations.

Some of these classes, such as the Figure class, store geometric object information. Other classes, such as Effect3D and FigureLayout, attach rendering attributes to instances of the geometric objects. Other classes describe the lighting and textures that a scene uses. The Graphics3D class handles all rendering operations, while a utility class (Util3D) provides methods that can help with the design of graphics algorithms.

Mascot Capsule V3 also has a graphics pipeline, similar to those found in other 3D-graphics environments such as OpenGL. This means that graphics commands are fed into a queue leading to the rendering engine where they are executed. Mascot Capsule V3 operates in an immediate mode where all graphics commands entering the pipeline are executed immediately upon the receipt of a flush command. Some Mascot Capsule V3 commands consist of graphics primitives or descriptions of a model's 3D geometry. Other commands configure the graphics environment and manage the rendering process.

Mascot Capsule V3 lets you import model geometry data that was generated by 3D-authoring programs. The exported data is stored as a resource in the mobile application's JAR file. The application then loads this resource into an instance of a Figure. The Figure class also helps implement animation effects for a model. The model's current arrangement of 3D objects represents a pose. An action description class, ActionTable, is associated with the Figure and stores the motions that change the model's pose. The action tables animate the model by changing its pose over time under program control. Animation effects generated in 3D-authoring programs can be exported as ActionTables whose files are also stored as JAR file resources. When imported by the application, these Actiontables animate the models.

JSR 184

JSR 184 is a J2ME-based standard developed under the guidance of the Java Community Process (JCP), a consortium of companies of which Sony Ericsson is a member (http://www.jcp.org/en/jsr/detail?id=184). JSR 184 was crafted to provide a wide set of 3D-rendering features that function within J2ME's constrained resources.

The JSR 184 implementation consists of API classes and a rendering engine. The rendering engine is software-only, although it was designed so that its architecture can take advantage of any FPU or graphics accelerators if present.

JSR 184's API follows typical 3D conventions in that you specify floating-point values for coordinates, transforms, and other 3D information. The floating-point numbers must conform to the Java language's float data type. By using floating-point arguments, JSR 184 simplifies the porting of code based on OpenGL or other 3D APIs.

Compared to Mascot Capsule V3, JSR 184 has a bevy of classes—30 in all (Figure 2). JSR 184 takes the kitchen-sink approach of providing a wide variety of features. The many classes let you use just a few API calls to execute more specialized 3D operations—not just the common ones. They allow you to design complex scenery using meshes and other objects to build a landscape, illuminate it with multiple lights, and view the scene from different angles using multiple cameras. A detailed description of these objects is beyond the scope of this article.

JSR 184 supports both retained and immediate modes of operation. Retained mode uses what's known as a "scene graph" to link all of the geometric objects in a 3D world through a structured tree of nodes. You use objects subclassed from Node (such as Lights, Sprites, and Meshes) to assemble the 3D world. The Group class lets you gather node objects together and organize them, and the World class defines a special Group node that acts as the top-level container for all of the nodes in the 3D world. To display a view of the 3D world using the retained mode, you execute a Graphics3D render() method on a World node. Like Mascot Capsule V3, JSR 184 supports AnimationController and AnimationTrack classes that are used to animate 3D models under program control.

JSR 184 also supports an immediate mode, where you invoke the render() method on submesh objects (actually objects that consist of arrays of vertex information and attributes). These two modes can execute on the same scene simultaneously. You can import a World that creates a virtual scene, then use the immediate mode to move sprites or other objects through it.

A Loader class enables JSR 184 to import 3D models and animations made by 3D-authoring programs with desktop PCs. The file format that's used to store 3D-geometry data, appearance attributes, texture maps, and other information is described in detail in the specification document.

Using the APIs

To add 3D capabilities to mobile applications (MIDlets), you simply add 3D API calls to the source code. No special linking or postprocessing is required.

Both APIs work by "binding" a single instance of the 3D-rendering environment to a J2ME LCD User Interface (LCDUI) Graphics object. The code renders the scene into this object and when done, it is released. Listings One(a) and One(b) illustrate this sequence for both APIs. While the principle is simple, the tricky part lies in the details that don't appear in the listing.

Often you use an instance of Canvas or GameCanvas as the Graphics binding target, as it provides low-level access to the phone's keypad, jog dial, or touch screen. You add a run() method that periodically updates the models in the 3D world, then calls repaint() to display the revised 3D content. The Canvas's paint() method is where you render the scene.

The set-up sequence takes six steps:

  1. Assemble or import the models.
  2. Set up textures and visual attributes for the models.
  3. Set up the camera.
  4. Set up the lights and any other effects.
  5. Set up the projection layout (parallel or perspective).
  6. Assign the active camera.

Now the scene can be rendered. Some of these steps will be part of the MIDlet's initialization code while others, such as the active camera choice, lighting, and effects, are in the paint() method.

Step 1 is to assemble or import your cast of 3D models. Listings Two(a) and (b) show how to use the immediate mode in Mascot Capsule V3 and JSR 184 to generate a 3D model of a cube. The JSR 184 procedure is more involved because the data has to be stored into instances of VertexArrays and then referenced by VertexBuffer. The additional effort is necessary because JSR 184 uses these objects to share texture and visual attributes with other models, thereby reducing memory usage for complex models.

For step 2, you assign visual attributes for the models and set up any texture maps. Listings Three(a) and (b) show how to load and assign the textures in these APIs. For Mascot Capsule V3, you make instances of a Texture. This is also where you configure the visual attributes of the model with instances of Appearance and Material in JSR 184. Again, because of JSR 184's flexibility and wider range of functions, it requires that you supply more information to set up the model's visual characteristics.

With the cast of models and their appearances prepared, now it's onto step 3 to arrange the point of view (POV) for the scene. Mascot Capsule V3 has one camera, where you supply three vectors to a lookAt() method to configure the viewing position coordinates. JSR 184 defines a Camera class used for this purpose. A JSR 184 scene can have multiple instances of Camera with different POVs, although only one Camera can be active at a time. Listings Four(a) and (b) detail how this is done in both APIs. Note that in making a Camera, you also set the projection layout.

For step 4, you add lighting and effects to the stage. Listings Five(a) and (b) show how this is done. Mascot Capsule V3 has only two white lights, a directional one and an ambient one, that are part of a Light class. For effects, you make an Effect object and specify the color-shading process the scene uses. This is also where you fold in the lighting characteristics. JSR-184 provides a Light class that allows for multiple lights of different colors. You'll make an instance of Light, then supply its color and intensity.

It's time to specify the viewing geometry or projection model of the 3D space for step 5. The projection model type determines what visual corrections and transformations are applied to render a 2D image from the 3D scene. Each API supports two views: parallel and perspective.

Listings Six(a) and (b) show how the perspective projection model is set. For Mascot Capsule V3, this is accomplished by invoking the setPerspective() method call on an instance of FigureLayout. For JSR 184, invoking its setPerspective() method during the creation of the Camera does the job, as shown in Listing Four.

Now all that's left to do for step 6 is to assign the active camera and render the scene; see Listings Seven(a) and (b). For Mascot Capsule V3, first we assemble a graphics command, COMMAND, that specifies triangle primitives, no color, texture mapping, and other attributes to be dropped into the graphics pipeline. The renderPrimitives() method is invoked, and it is given the information stored in the Texture, FigureLayout, and Effect instances, along with the model geometry data in vert, norm, and tex.

For JSR 184, the Camera is positioned and chosen as the active camera with the setCamera() method. The Light is added and made active. Finally, the scene is rendered using the information in the instances of VertexBuffer, IndexBuffer, and Appearance. The operation occurs in immediate mode because it is invoked on a single object and not an instance of a World.

Interacting with the Models

As the listings suggest, you can also change lights, lighting values, the projection view, and other aspects of the scene on-the-fly. This allows for easy user interaction with the models. For example, JSR 184's render() method also accepts a transformation matrix as an argument, which lets you rotate, scale, or move the model. For Mascot Capsule V3, you can apply a transformation matrix to the instance of FigureLayout, which then affects the model during rendering.

Through the Canvas class's event methods, you capture keystrokes on the phone's keypad. This input can be used to modify a transformation array so that the model responds in a specific way to the key press. For example, pressing certain keys could cause a race car to move left or right. Or, they might make a spaceship rotate about. The two demo programs that accompany this article (available electronically; see "Resource Center," page 3) demonstrate how to import models from files, and you can move them about using the phone's keypad or navigation button.

As you better understand the capabilities of the APIs, you can apply more creative effects. By selecting a different Camera that contains a different POV, you can effortlessly switch between different views of the same scene (perhaps a view above a race car, where you can toggle to a second view from the driver's seat) or in JSR 184, turn on/off different "banks" of colored lights.

From the previous discussion, you can see that both Mascot Capsule V3 and JSR 184 provide capable 3D-graphic environments. If your 3D-graphics requirements are modest, Mascot Capsule V3 is a suitable API. It also has better performance than the JSR 184 implementation on current phones in the market. MIDlets with more sophisticated graphics requirements, or those applications using less common 3D functions, are best served by JSR 184. Sony Ericsson's phones, by carrying both 3D APIs, have become the labs where next-generation 3D applications will be written. For more information on mobile Java 3D development, see http://www.SonyEricsson.com/developer/java3d/.

DDJ



Listing One (a)

// Mascot Capsule v3

public class CoolGameCanvas extends Canvas
{
// Get instance of 3D environment
   Graphics3D gameG3D = new Graphics3D();
// Paint method which handle 3D content display
   public void paint(Graphics g) {
   try {
      gameG3D.bind(g);
      // update the scene elements
      // render the scene
   } finally {
      gameG3D.release();
   }
} // end CoolGameCanvas

(b)
// JSR 184

public class CoolGameCanvas extends Canvas
{
// Get instance of 3D environment
   Graphics3D gameG3D = Graphics3D.getInstance();
// Paint method which handle 3D content display
   public void paint(Graphics g) {
   try {
      gameG3D.bindTarget(g);
      // update the scene elements
      // render the scene
   } finally {
      gameG3D.releaseTarget();
   }
} // end CoolGameCanvas
Back to article


Listing Two (a)
// Mascot Capsule v3

// Triangles that make up the cube's faces. Are vertices, not triangle strips
   private int[] vert = { 
    10, 10, 10, -10, 10, 10,  10,-10, 10,  -10, 10, 10, 10,-10, 10, 
                                                        -10,-10, 10, // front
    -10, 10,-10, 10, 10,-10,  -10,-10,-10, 10, 10,-10,  -10,-10,-10, 
                                                       10,-10,-10,   // back
    -10, 10, 10, -10, 10,-10, -10,-10, 10, -10, 10,-10, -10,-10, 10,   
                                                       -10,-10,-10,  // left
    10, 10,-10,  10, 10, 10,  10,-10,-10,  10, 10, 10,  10,-10,-10,  
                                                       10,-10, 10,   // right
    10, 10,-10, -10, 10,-10,  10, 10, 10,  -10, 10,-10, 10, 10, 10,  
                                                      -10, 10, 10,   // top
    10,-10, 10, -10,-10, 10,  10,-10,-10,  -10,-10, 10, 10,-10,-10,   
                                                      -10,-10,-10 }; // bottom
// Cube's normals -- Used in lighting calculations.
private int[] norm = {  
0, 0, 4096,  0, 0, 4096,  0, 0, 4096,  0, 0, 4096,  0, 0, 4096,  0, 0, 4096,
0, 0, -4096, 0, 0,-4096,  0, 0,-4096,  0, 0,-4096,  0, 0,-4096,  0, 0, -4096,
-4096, 0, 0, -4096, 0, 0, -4096, 0,  0,-4096, 0, 0, -4096, 0, 0, -4096, 0,  0,
4096, 0, 0,  4096, 0, 0,  4096, 0, 0,  4096, 0, 0,  4096, 0, 0,  4096, 0,  0,
0, 4096, 0,  0, 4096, 0,  0, 4096, 0,  0, 4096, 0,  0, 4096, 0,  0, 4096, 0,
0, -4096, 0, 0, -4096, 0, 0, -4096, 0, 0, -4096, 0, 0, -4096, 0, 0, -4096, 0};

(b)
// JSR 184

private VertexBuffer    iVb;  // Vertex positions, normals, colors, texcoords
private IndexBuffer     iIb;  // Indices to VertexBuffer, for tri-strips
// Triangles strips that make up the cube's faces
   short[] vert = { 
      10, 10, 10,   -10, 10, 10,    10,-10, 10,   -10,-10, 10,   // front
     -10, 10,-10,    10, 10,-10,   -10,-10,-10,    10,-10,-10,   // back
     -10, 10, 10,   -10, 10,-10,   -10,-10, 10,   -10,-10,-10,   // left
      10, 10,-10,    10, 10, 10,    10,-10,-10,    10,-10, 10,   // right
      10, 10,-10,   -10, 10,-10,    10, 10, 10,   -10, 10, 10,   // top
      10,-10, 10,   -10,-10, 10,    10,-10,-10,   -10,-10,-10 }; // bottom
 
// Create a VertexArray to hold the vertices for the object
   VertexArray vertArray = new VertexArray( vert.length / 3, 3, 2 );
   vertArray.set( 0, vert.length/3, vert );
 
// The per-vertex normals for the cube 
   byte[] norm = {0, 0, 127,      0, 0, 127,     0, 0, 127,     0, 0, 127,
                 0, 0, -127,      0, 0,-127,     0, 0,-127,    0, 0, -127,
                 -127, 0, 0,     -127, 0, 0,   -127, 0,  0,   -127, 0,  0,
                  127, 0, 0,      127, 0, 0,     127, 0, 0,    127, 0,  0,
                  0, 127, 0,      0, 127, 0,     0, 127, 0,     0, 127, 0,
                 0, -127, 0,     0, -127, 0,    0, -127, 0,   0, -127, 0};
// Create a vertex array for the normals of the object.
   VertexArray normArray = new VertexArray( norm.length / 3, 3, 1 );
   normArray.set( 0, norm.length/3, norm );
   int[] stripLen = { 4, 4, 4, 4, 4, 4 };  // Length of each triangle strip
// Create the VertexBuffer for our object
   VertexBuffer vb = iVb = new VertexBuffer();
   vb.setPositions(vertArray, 1.0f, null);       // Unit scale, zero bias
   vb.setNormals(normArray);
 
// Create the index buffer for the object (this tells how to create triangle
// strips from the contents of the vertex buffer).
   iIb = new TriangleStripArray( 0, stripLen );
Back to article


Listing Three (a)
// Mascot Capsule v3

private static Texture d1_texture;
   d1_texture = new Texture("tex_skin.bmp", true);
// Coordinates on 2D BMP image to be mapped onto the cube's faces.
   private int[] tex = {  
     96, 32,      64, 32,      96, 64,       64, 32,      96, 64,    64, 64,
     64, 32,      32, 32,      64, 64,       32, 32,      64, 64,    32, 64,
     64, 0,       32, 0,       64, 32,       32, 0,       64, 32,    32, 32,
     32, 0,        0, 0,       32, 32,        0, 0,       32, 32,     0, 32,
     32, 32,       0, 32,      32, 64,        0, 32,      32, 64,     0, 64, 
     96, 0,       64, 0,       96, 32,       64, 0,       96, 32,    64, 32 };
   private int [] kolor = {  0xFF00FF }; // Placeholder

(b)
// JSR 184

private Image           iImage;
private Appearance      iAppearance;  // For material, texture, compositing
// Vertex texture coordinates. These specify the coordinates on a 2-D image
//   that will be painted onto a surface.
   short[] tex = {  
     96, 32,       64, 32,       96, 64,       64, 64,
     64, 32,       32, 32,       64, 64,       32, 64,
     64, 0,        32, 0,        64, 32,       32, 32,
     32, 0,         0, 0,        32, 32,        0, 32,
     32, 32,        0, 32,       32, 64,        0, 64,
     96, 0,        64, 0,        96, 32,       64, 32 };
// Create a vertex array for the texture coordinates of the object
     VertexArray texArray = new VertexArray( tex.length / 2, 2, 2 );
     texArray.set(0, tex.length/2, tex);
// Place texture coords into instance of VertexBuffer made in Listing 2 
     vb.setTexCoords(0, texArray, (1.0f/128.0f), null);  
                                         // 128-pixel scale, zero bias
// Load the image for the texture
     iImage = Image.createImage("/cubeface.png");
// Create the Image2D (we need this so we can make a Texture2D)
     Image2D image2D = new Image2D( Image2D.RGB, iImage );
// Create the Texture2D and enable mipmapping
    Texture2D texture = new Texture2D( image2D );
    texture.setFiltering( Texture2D.FILTER_NEAREST,Texture2D.FILTER_NEAREST );
    texture.setWrapping( Texture2D.WRAP_CLAMP, Texture2D.WRAP_CLAMP );
    texture.setBlending( Texture2D.FUNC_MODULATE );
// Create the appearance
     iAppearance = new Appearance();
     iAppearance.setTexture( 0, texture ); // Add Texture2D to the Appearance
     iAppearance.setMaterial(iMaterial);
     iMaterial.setVertexColorTrackingEnable( true ); // Track per-vertex color
     iMaterial.setColor(Material.SPECULAR, 0xFFFFFFFF);  // Specular = white
     iMaterial.setShininess(100.0f);
Back to article


Listing Four (a)
//Mascot Capsule v3

// Vectors for point of camera point of view
private Vector3D D1_POS = new Vector3D(0,30,50);
private Vector3D D1_LOOK = new Vector3D(0,-2048,-4096);
private Vector3D D1_UP = new Vector3D(0,4096,0);

// Set up camera and rotation transform
     AffineTrans d1_vtrans = new AffineTrans();
     d1_vtrans.setIdentity();
     d1_vtrans.lookAt(D1_POS, D1_LOOK, D1_UP);

(b)
// JSR 184

private Camera iCamera;
// Create a camera
     iCamera = new Camera();
     iCamera.setPerspective(80.0f,              // field of view
         (float)getWidth()/ (float)getHeight(),  // aspectRatio
         1.0f,      // near clipping plane
         1000.0f);  // far clipping plane
Back to article


Listing Five (a)
// Mascot Capsule v3

Effect3D effect;
// Lights settings. Only two lights: directional and ambient
// Directional light, vector and intesity
     private Vector3D lightDirVec = new Vector3D(-146,293,439);
     private int lightDir = 3730;
// Ambient light intensity
     private int lightAmbi = 1626;
//  Make the lights
     private Light d1_light = new Light(lightDirVec,lightDir,lightAmbi);
//  Effects set up
     effect = new Effect3D();
     effect.setShadingType(Effect3D.NORMAL_SHADING);
     effect.setLight(d1_light);

(b)
// JSR 184

private Light  iLight;
// Create a light
     iLight = new Light();
     iLight.setColor( 0xffffff );         // White
     iLight.setIntensity(1.25f);          // Over bright
Back to article


Listing Six (a)
// Mascot Capsule v3

private int D1_pers = 512;    // Equivalent to 45 degrees
FigureLayout d1_layout;
// Set up Figure's (cube) layout
     d1_layout = new FigureLayout();
     d1_layout.setPerspective(10, 4096, D1_pers);
     d1_layout.setCenter(getWidth()/2, getHeight()/2); // Center on screen

(b)
// Accomplished in Listing 4.
Back to article


Listing Seven (a)
// Mascot Capsule v3

// Constants for graphics commands
private static final int COMMAND = Graphics3D.PRIMITVE_TRIANGLES |
     Graphics3D.PDATA_NORMAL_PER_VERTEX |
     Graphics3D.PDATA_TEXURE_COORD |
     Graphics3D.PDATA_COLOR_NONE |
     Graphics3D.ENV_ATTR_LIGHTING | Graphics3D.PATTR_BLEND_HALF;
// Instantiate 3D graphics object       
     Graphics3D g3d = new Graphics3D();
// Draw the scene           
     try{
        g3d.bind(g);
        g3d.renderPrimitives(d1_texture, 0, 0, d1_layout, effect, COMMAND, 12,
                             vert, norm, tex, kolor);
        g3d.flush();
        g3d.release( g );           
        } catch (Throwable h){
            h.printStackTrace();
        } // end catch

(b)
// JSR 184

private Graphics3D iG3D;
// Set up the camera in the desired position
     Transform transform3D = new Transform();
     transform3D.postTranslate(0.0f, 0.0f, 30.0f);
// Get the singleton Graphics3D instance
     iG3D = Graphics3D.getInstance();
     iG3D.setCamera(iCamera, transform3D);
     iG3D.resetLights();
     iG3D.addLight(iLight, transform3D);
// Render our cube
     iG3D.render(iVb, iIb, iAppearance, null);
// Free the graphics object
     iG3D.releaseTarget();
Back to article


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.