In the last tutorial, I showed how to lay the groundwork to get an O3D application going. Now, I think, it is time to actually make it do something.

We are going to show a spinning cube. The code here can be found almost verbatim in the O3D samples – I only made a few minor modifications to it. I figured that any code I would write my self would be no improvement over it since it fits so perfectly with this tutorial.

The basic framework is identical to Tutorial 1, but have now added the extra code to show the cube.

<html>
   <head>
      <meta http-equiv="content-type" content="text/html; charset=UTF-8">
      <title>Tutorial 2: Drawing a cube</title>
      <script type="text/javascript" src="o3djs/base.js"></script>
      <script type="text/javascript">
         o3djs.require('o3djs.util');
         o3djs.require('o3djs.math');
         o3djs.require('o3djs.rendergraph');

         // Events
         // Run the init() function once the page has finished loading.
         // Run the uninit() function when the page has is unloaded.
         window.onload = init;
         window.onunload = uninit;

         // global variables
         var g_o3d;
         var g_math;
         var g_client;
         var g_pack;
         var g_clock = 0;
         var g_timeMult = 1;
         var g_cubeTransform;

         function createCube(material) {
           var cubeShape = g_pack.createObject('Shape');
           var cubePrimitive = g_pack.createObject('Primitive');
           var streamBank = g_pack.createObject('StreamBank');

           cubePrimitive.material = material;
           cubePrimitive.owner = cubeShape;
           cubePrimitive.streamBank = streamBank;

           cubePrimitive.primitiveType = g_o3d.Primitive.TRIANGLELIST;
           cubePrimitive.numberPrimitives = 12; // 12 triangles
           cubePrimitive.numberVertices = 8;    // 8 vertices in total

           var positionArray = [
             -0.5, -0.5,  0.5,  // vertex 0
              0.5, -0.5,  0.5,  // vertex 1
             -0.5,  0.5,  0.5,  // vertex 2
              0.5,  0.5,  0.5,  // vertex 3
             -0.5,  0.5, -0.5,  // vertex 4
              0.5,  0.5, -0.5,  // vertex 5
             -0.5, -0.5, -0.5,  // vertex 6
              0.5, -0.5, -0.5   // vertex 7
           ];

           var indicesArray = [
               0, 1, 2,  // face 1
               2, 1, 3,
               2, 3, 4,  // face 2
               4, 3, 5,
               4, 5, 6,  // face 3
               6, 5, 7,
               6, 7, 0,  // face 4
               0, 7, 1,
               1, 7, 3,  // face 5
               3, 7, 5,
               6, 0, 4,  // face 6
               4, 0, 2
           ];

           var positionsBuffer = g_pack.createObject('VertexBuffer');
           var positionsField = positionsBuffer.createField('FloatField', 3);
           positionsBuffer.set(positionArray);

           var indexBuffer = g_pack.createObject('IndexBuffer');
           indexBuffer.set(indicesArray);

           streamBank.setVertexStream(
               g_o3d.Stream.POSITION, //  This stream stores vertex positions
               0,                     // First (and only) position stream
               positionsField,        // field: the field this stream uses.
               0);                    // start_index:
           // Associate the triangle indices Buffer with the primitive.
           cubePrimitive.indexBuffer = indexBuffer;

           return cubeShape;
         }

         /**
          * This method gets called every time O3D renders a frame.
          * Here's where we update the cube's transform to make it spin.
          * @param {o3d.RenderEvent} renderEvent The render event object
          * that gives us the elapsed time since last time a frame was rendered.
          */
         function renderCallback(renderEvent) {
           g_clock += renderEvent.elapsedTime * g_timeMult;
           // Rotate the cube around the Y axis.
           g_cubeTransform.identity();
           g_cubeTransform.rotateY(2.0 * g_clock);
         }

         /**
          * Creates the client area.
          */
         function init() {
           o3djs.util.makeClients(initStep2);
         }

         /**
          * Initializes O3D.
          * @param {Array} clientElements Array of o3d object elements.
          */
         function initStep2(clientElements) {
           // Initializes global variables and libraries.
           var o3dElement = clientElements[0];
           g_client = o3dElement.client;
           g_o3d = o3dElement.o3d;
           g_math = o3djs.math;

           // Initialize O3D sample libraries.
           o3djs.base.init(o3dElement);

           // Create a pack to manage the objects created.
           g_pack = g_client.createPack();

           // Create the render graph for a view.
           var viewInfo = o3djs.rendergraph.createBasicView(
               g_pack,
               g_client.root,
               g_client.renderGraphRoot);

            // Set up a simple orthographic view.

           viewInfo.drawContext.projection = g_math.matrix4.perspective(
               g_math.degToRad(30), // 30 degree fov.
               g_client.width / g_client.height,
               1,                  // Near plane.
               5000);              // Far plane.

           // Set up our view transformation to look towards the world origin
           // where the cube is located.
           viewInfo.drawContext.view = g_math.matrix4.lookAt([0, 1, 5], // eye
                                                     [0, 0, 0],  // target
                                                     [0, 1, 0]); // up

           // Create an Effect object and initialize it using the shaders
           // from the text area.
           var redEffect = g_pack.createObject('Effect');
           var shaderString = document.getElementById('effect').value;
           redEffect.loadFromFXString(shaderString);

           // Create a Material for the mesh.
           var redMaterial = g_pack.createObject('Material');

           // Set the material's drawList.
           redMaterial.drawList = viewInfo.performanceDrawList;

           // Apply our effect to this material. The effect tells the 3D
           // hardware which shaders to use.
           redMaterial.effect = redEffect;

           // Create the Shape for the cube mesh and assign its material.
           var cubeShape = createCube(redMaterial);

           // Create a new transform and parent the Shape under it.
           g_cubeTransform = g_pack.createObject('Transform');
           g_cubeTransform.addShape(cubeShape);

           // Parent the cube's transform to the client root.
           g_cubeTransform.parent = g_client.root;

           // Generate the draw elements for the cube shape.
           cubeShape.createDrawElements(g_pack, null);

           // Set our render callback for animation.
           // This sets a function to be executed every time frame is rendered.
           g_client.setRenderCallback(renderCallback);
         }

         /**
          * Removes any callbacks so they not called after the page has unloaded.
          */
         function uninit() {
           if (g_client) {
             g_client.cleanup();
           }
         }

      </script>
   </head>
   <body>
      <h1>Tutorial 2: Drawing a cube</h1>
      We now put our application to good use by drawing a spinning cube.
      <br/>

      <div id="o3d" style="width: 300px; height: 300px;"></div>
      <div style="display:none">
         <!-- Start of effect -->
         <textarea id="effect">
           // World View Projection matrix that will transform the input
           // vertices to screen space.
           float4x4 worldViewProjection : WorldViewProjection;

           // input parameters for our vertex shader
           struct VertexShaderInput {
             float4 position : POSITION;
           };

           // input parameters for our pixel shader
           struct PixelShaderInput {
             float4 position : POSITION;
           };

           /**
            * The vertex shader transforms the input vertices to screen space.
            */
           PixelShaderInput vertexShaderFunction(VertexShaderInput input) {
             PixelShaderInput output;

             // Multiply the vertex positions by the worldViewProjection matrix
             // to transform them to screen space.
             output.position = mul(input.position, worldViewProjection);
             return output;
           }

           /**
            * This pixel shader just returns the color red.
            */
           float4 pixelShaderFunction(PixelShaderInput input): COLOR {
             return float4(1, 0, 0, 1);  // Red.
           }

           // Here we tell our effect file *which* functions are
           // our vertex and pixel shaders.

           // #o3d VertexShaderEntryPoint vertexShaderFunction
           // #o3d PixelShaderEntryPoint pixelShaderFunction
           // #o3d MatrixLoadOrder RowMajor
         </textarea>
         <!-- End of effect -->
    </div>
  </body>
</html>


Now let’s look at what we have added. First, we tell o3d to include the graphics renderer with

o3djs.require('o3djs.rendergraph');


Next, we have added a few more global variables.

         var g_pack;
         var g_clock = 0;
         var g_timeMult = 1;
         var g_cubeTransform;



Before I go onto the cube function, I will address the extra lines added to the init function, which has bulked up slightly.

Le’ts go through what is happening here. First the pack is created, which manages all the objects which are created.

           g_pack = g_client.createPack();


The next few lines then set up the view, which in this case is a 30 degree field of view,  looking towards the origin ([0, 0, 0]).

           var viewInfo = o3djs.rendergraph.createBasicView(
               g_pack,
               g_client.root,
               g_client.renderGraphRoot);

           // Set up a simple orthographic view.
           viewInfo.drawContext.projection = g_math.matrix4.perspective(
               g_math.degToRad(30), // 30 degree fov.
               g_client.width / g_client.height,
               1,                  // Near plane.
               5000);              // Far plane.

           // Set up our view transformation to look towards the world origin
           // where the cube is located.
           viewInfo.drawContext.view = g_math.matrix4.lookAt([0, 1, 5], // eye
                                                     [0, 0, 0],  // target
                                                     [0, 1, 0]); // up


The following 3 lines load the effect string stored in the HTML text area into the effect object. This will contain the effect we want to apply to the cube, which in this case is just a red colour.

           var redEffect = g_pack.createObject('Effect');
           var shaderString = document.getElementById('effect').value;
           redEffect.loadFromFXString(shaderString);


We now create the material in the following 3 lines, which is what is going to tell our cube what it is supposed to look like.

           var redMaterial = g_pack.createObject('Material');
           redMaterial.drawList = viewInfo.performanceDrawList;
           redMaterial.effect = redEffect;


Next we call the function that generates the necessary data to display a cube, which I will more details for in the next section.

          var cubeShape = createCube(redMaterial);


We now have to add the draw elements which make up the cube to the pack, which is what will be used to actually display the cube

           cubeShape.createDrawElements(g_pack, null);


We then add the cube to the client area using the transform object

           g_cubeTransform = g_pack.createObject('Transform');
           g_cubeTransform.addShape(cubeShape);
           g_cubeTransform.parent = g_client.root;



And finally add a call to the render callback, which will then loop updating the scene every frame.

           g_client.setRenderCallback(renderCallback);



Now we define the createCube(material) that defines the cube which we would like to create. It takes the parameter of the material which we spoke about in the above code.

First off, we create a few objects which we will use to create the cube. cubeShape is the object we are going to return. cubePrimitive is primitive object, which in our case will be a triangle list. streamBank is a stream containing the vertices we have created.

           var cubeShape = g_pack.createObject('Shape');
           var cubePrimitive = g_pack.createObject('Primitive');
           var streamBank = g_pack.createObject('StreamBank');



Next, we assign the material to the primitive and set the owner and streamBank of the primitive. This links the primitive to the cubeShape object which we are going to return.

           cubePrimitive.material = material;
           cubePrimitive.owner = cubeShape;
           cubePrimitive.streamBank = streamBank;



We now set the primitive type to a triangle list, made up of 12 triangles (a cube has 6 sides with 2 triangles each for the surfaces) and 8 vertices.

           cubePrimitive.primitiveType = g_o3d.Primitive.TRIANGLELIST;
           cubePrimitive.numberPrimitives = 12; // 12 triangles
           cubePrimitive.numberVertices = 8;    // 8 vertices in total



After this we create an array containing the coordinates of of each of the vertices in the cube. By playing around with these values we can change the shape of the object, although the it would still be an object made up of 8 vertices

          var positionArray = [
             -0.5, -0.5,  0.5,  // vertex 0
              0.5, -0.5,  0.5,  // vertex 1
             -0.5,  0.5,  0.5,  // vertex 2
              0.5,  0.5,  0.5,  // vertex 3
             -0.5,  0.5, -0.5,  // vertex 4
              0.5,  0.5, -0.5,  // vertex 5
             -0.5, -0.5, -0.5,  // vertex 6
              0.5, -0.5, -0.5   // vertex 7
           ];



Now we set up the triangles which will make faces of the cubes.Each set of 3 numbers are the vertices of a triangle.  An important point here is that the order in which you specify the vertices matters. The vertices should always be specified in a clockwise direction. It will soon become apparent if you got the order mixed up, because if the order is wrong, the renderer will think that the triangle is facing away from you, and thus will not be drawn. If you find that you end up with invisible triangles, this is likely what has happened. Don’t feel bad….it has happened to me many times.

          var indicesArray = [
               0, 1, 2,  // face 1
               2, 1, 3,
               2, 3, 4,  // face 2
               4, 3, 5,
               4, 5, 6,  // face 3
               6, 5, 7,
               6, 7, 0,  // face 4
               0, 7, 1,
               1, 7, 3,  // face 5
               3, 7, 5,
               6, 0, 4,  // face 6
               4, 0, 2
           ];



We now add the vertices we have defined into a vertex buffer

           var positionsBuffer = g_pack.createObject('VertexBuffer');
           var positionsField = positionsBuffer.createField('FloatField', 3);
           positionsBuffer.set(positionArray);



And create a similar index buffer for the triangle indices

           var indexBuffer = g_pack.createObject('IndexBuffer');
           indexBuffer.set(indicesArray);



We now assign the vertex stream and index buffer

          streamBank.setVertexStream(
               g_o3d.Stream.POSITION, //  This stream stores vertex positions
               0,                     // First (and only) position stream
               positionsField,        // field: the field this stream uses.
               0);                    // start_index:
           // Associate the triangle indices Buffer with the primitive.
           cubePrimitive.indexBuffer = indexBuffer;

And lastly return the object we have created

           return cubeShape;



The next function to look at is the renderCallBack() function is used to update the screen each frame. All this does is rotates the cube about the Y axis by a set amount every time the function fires.

         function renderCallback(renderEvent) {
           g_clock += renderEvent.elapsedTime * g_timeMult;
           // Rotate the cube around the Y axis.
           g_cubeTransform.identity();
           g_cubeTransform.rotateY(2.0 * g_clock);
         }



The last section we need to look at is the effect. We create a textarea, into which we put the code to create whichever effect we are after. This is then read into the effect object in the Javascript. I will leave the details of how effects work for a later tutorial, but will cover the basics.

So, the first thing we need to do is define the world view projection, which sets up how the screen space is computed.

           float4x4 worldViewProjection : WorldViewProjection;



We then set up a few structs for the input parameters for the vertex and pixel shaders

           struct VertexShaderInput {
             float4 position : POSITION;
           };

           struct PixelShaderInput {
             float4 position : POSITION;
           };

           /**
            * The vertex shader transforms the input vertices to screen space.
            */



We now define a function that transforms the input vertices to the screen space, which means we are calculating how the object is meant to be drawn on screen.

           PixelShaderInput vertexShaderFunction(VertexShaderInput input) {
             PixelShaderInput output;
             output.position = mul(input.position, worldViewProjection);
             return output;
           }



The pixel shader returns what color to set each pixel to, which in our case is simply the colour red.

           float4 pixelShaderFunction(PixelShaderInput input): COLOR {
             return float4(1, 0, 0, 1);  // Red.
           }



This last portion assigns the functions we created above to the relevant entry points in o3d, so that they will be called correctly. And yes, these lines do look like they are commented out, but do not be fooled. they are very necessary.

           // #o3d VertexShaderEntryPoint vertexShaderFunction
           // #o3d PixelShaderEntryPoint pixelShaderFunction
           // #o3d MatrixLoadOrder RowMajor



If you now run the code in a web browser, if all has gone well, you should get a nice rotating cube in your browser.

Share