Now we are going to play around a bit with more than one light.

The way to do this is to define another set of lighting variables in the shader for the second light and then add in the values within the pixel shader for the second light.

The changes to the Javascript code are minimal. We now define 2 sets of global variables to define the lights location and colour.

var g_lightPosition1 = [10, 300, 7];
var g_lightColor1 = [0, 1, 1, 1];

var g_lightPosition2 = [-30, 40, 30];
var g_lightColor2 = [1, 0, 0, 1];

Then the only other function that needs to change is createPhoneMaterial. Here we now reference our updated shader, and then pass it the new parameters which we have set up. The emissive, ambient, diffuse, specular, shininess and colourMult values are common for the object, and are not related to the lights themselves so only have to be specified once.

function createPhongMaterial(baseColor) {
   // Load effect
  var effect = g_pack.createObject('Effect');
  var shaderString = 'shaders/phongtexmultilight.shader';
  o3djs.effect.loadEffect(effect, shaderString);

  // Create a new, empty Material object.
  var material = g_pack.createObject('Material');

  material.drawList = g_viewInfo.performanceDrawList;

  material.effect = effect;
  effect.createUniformParameters(material);

  // Assign parameters to the phong material.
  material.getParam('emissive').value = [0, 0, 0, 1];
  material.getParam('ambient').value = g_math.mulScalarVector(0.1, baseColor);
  material.getParam('diffuse').value = g_math.mulScalarVector(0.9, baseColor);
  material.getParam('specular').value = [.2, .2, .2, 1];
  material.getParam('shininess').value = 20;
  material.getParam('lightIntensity1').value = g_lightColor1;
  material.getParam('lightWorldPos1').value = g_lightPosition1;
  material.getParam('ambientIntensity').value = [0.2, 0.2, 0.2, 1];

  material.getParam('colorMult').value = [1, 1, 1, 1];

  material.getParam('lightIntensity2').value = g_lightColor2;
  material.getParam('lightWorldPos2').value = g_lightPosition2;

  var samplerParam = material.getParam('texSampler0');
  g_sampler = g_pack.createObject('Sampler');
  g_sampler.minFilter = g_o3d.Sampler.ANISOTROPIC;
  g_sampler.maxAnisotropy = 4;
  samplerParam.value = g_sampler;

  return material;
}


The real heavy lifting happens in the shader though, which I have named phongtexmultilight.shader.

uniform float4x4 worldViewProjection : WORLDVIEWPROJECTION;
uniform float4x4 world : WORLD;
uniform float4x4 viewInverse : VIEWINVERSE;
uniform float4x4 worldInverseTranspose : WORLDINVERSETRANSPOSE;
uniform float3 lightWorldPos1;
uniform float4 lightIntensity1;
uniform float3 lightWorldPos2;
uniform float4 lightIntensity2;
uniform float4 ambientIntensity;
uniform float4 ambient;
uniform float4 emissive;
uniform float4 colorMult;
uniform float4 diffuse;
uniform float4 specular;
uniform float shininess;
sampler texSampler0;

struct VertexShaderInput {
  float4 position : POSITION;
  float4 normal : NORMAL;
  float2 tex : TEXCOORD0;
};

struct PixelShaderInput {
  float4 position : POSITION;
  float3 normal : TEXCOORD1;
  float3 worldPosition : TEXCOORD4;
  float2 tex : TEXCOORD0;
};

PixelShaderInput vertexShaderFunction(VertexShaderInput input) {
  PixelShaderInput output;
  output.position = mul(input.position, worldViewProjection);
  float3 worldPosition = mul(input.position, world).xyz;
  output.normal = mul(input.normal, worldInverseTranspose).xyz;
  output.worldPosition = worldPosition;
  output.tex = input.tex;
  return output;
}

float4 pixelShaderFunction(PixelShaderInput input) : COLOR {
  float4 textureCooef = tex2D(texSampler0, input.tex);
  float3 surfaceToLight1 = normalize(lightWorldPos1 - input.worldPosition);
  float3 surfaceToLight2 = normalize(lightWorldPos2 - input.worldPosition);
  float3 worldNormal = normalize(input.normal);
  float3 surfaceToView = normalize(viewInverse[3].xyz - input.worldPosition);
  float3 halfVector1 = normalize(surfaceToLight1 + surfaceToView);
  float3 halfVector2 = normalize(surfaceToLight2 + surfaceToView);
  float4 litResult1 = lit(dot(worldNormal, surfaceToLight1),
                         dot(worldNormal, halfVector1), shininess);
  float4 litResult2 = lit(dot(worldNormal, surfaceToLight2),
                         dot(worldNormal, halfVector2), shininess);
  float4 outColor1 = lightIntensity1 * ((diffuse * textureCooef) * colorMult * litResult1.y + specular * litResult1.z);
  float4 outColor2 = lightIntensity2 * ((diffuse * textureCooef) * colorMult * litResult2.y + specular * litResult2.z);

  float4 outColor = ambientIntensity * ambient * colorMult * textureCooef;
  outColor = outColor + outColor1 + outColor2;
  outColor += emissive;
  return float4(outColor.rgb, diffuse.a * colorMult.a);
}

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

The lines that are particularly important are these in the pixel shader. The surfaceToLight, halfVector and litResult vectors need to be calculated for both lights. Then the diffuse and specular lighting can be calculated for each light. The emissive and ambient light are calculated once, and then the results from the two lights are added to this, which gives the final lighting for that pixel.

  float3 surfaceToLight1 = normalize(lightWorldPos1 - input.worldPosition);
  float3 surfaceToLight2 = normalize(lightWorldPos2 - input.worldPosition);
  float3 worldNormal = normalize(input.normal);
  float3 surfaceToView = normalize(viewInverse[3].xyz - input.worldPosition);
  float3 halfVector1 = normalize(surfaceToLight1 + surfaceToView);
  float3 halfVector2 = normalize(surfaceToLight2 + surfaceToView);
  float4 litResult1 = lit(dot(worldNormal, surfaceToLight1),
                         dot(worldNormal, halfVector1), shininess);
  float4 litResult2 = lit(dot(worldNormal, surfaceToLight2),
                         dot(worldNormal, halfVector2), shininess);
  float4 outColor1 = lightIntensity1 * ((diffuse * textureCooef) * colorMult * litResult1.y + specular * litResult1.z);
  float4 outColor2 = lightIntensity2 * ((diffuse * textureCooef) * colorMult * litResult2.y + specular * litResult2.z);

  float4 outColor = ambientIntensity * ambient * colorMult * textureCooef;
  outColor = outColor + outColor1 + outColor2;
  outColor += emissive;
  return float4(outColor.rgb, diffuse.a * colorMult.a);

And after that is all done and in place, we should be able to see something similar to this

O3D terrain under 2 lights

O3D terrain under 2 lights

Here is the full listing for the javascript file

o3djs.require('o3djs.util');
o3djs.require('o3djs.math');
o3djs.require('o3djs.rendergraph');
o3djs.require('o3djs.canvas');
o3djs.require('o3djs.quaternions');
o3djs.require('o3djs.event');
o3djs.require('o3djs.arcball');
o3djs.require('o3djs.primitives');
o3djs.require('o3djs.picking');
o3djs.require('o3djs.io');
o3djs.require('o3djs.pack');
o3djs.require('o3djs.scene');

// 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_o3dElement;
var g_o3d;
var g_math;
var g_client;
var g_pack;
var g_clock = 0;
var g_timeMult = 1;
var g_landscapeTransform;
var g_textCanvas;
var g_paint;
var g_canvasLib;
var g_3dRoot;
var g_hudRoot;
var g_viewInfo;
var g_hudViewInfo;
var g_keyPressDelta = 0.05;

var g_quaternions;
var g_aball;
var g_thisRot;
var g_lastRot;
var g_dragging = false;
var g_lightPosition1 = [10, 300, 7];
var g_lightColor1 = [0, 1, 1, 1];

var g_lightPosition2 = [-30, 40, 30];
var g_lightColor2 = [1, 0, 0, 1];

var g_camera = {
  eye: [0, 200, 10],
  target: [0, 0, 0]
};
var g_mouseX = 0;
var g_mouseY = 0;
var g_spinningObject = false;
var g_pickedInfo;
var g_treeInfo;
var g_sampler;
var g_backgroundCanvas;
var g_backgroundRoot;
var g_backgroundViewInfo;
var g_backgroundCanvasLib;
var g_backgroundTexture;

var g_lastMouseX = 0;
var g_lastMouseY = 0;
var g_mouseXDelta = 0;
var g_mouseYDelta = 0;
var g_rotationDelta = 0.002;
var g_translationDelta = 0.2;
var g_mouseLocked = false;
var g_lookingDir = [0, 0, 0];

var g_htmlImage;
var g_heightMapCanvas;
var g_heightMapCanvasContext;
var g_heightMapImageData;
var g_landscapeLoaded = false;
var g_heightMapLoaded = false;
var g_initRun = false;

function createLandscape(material) {
  var vertexInfo = o3djs.primitives.createVertexInfo();
  var positionStream = vertexInfo.addStream(
      3, o3djs.base.o3d.Stream.POSITION);
  var normalStream = vertexInfo.addStream(
      3, o3djs.base.o3d.Stream.NORMAL);
  var texCoordStream = vertexInfo.addStream(
      2, o3djs.base.o3d.Stream.TEXCOORD, 0);

  var color;
  var avgColor;

  var startX = -50;
  var startY = 0;
  var startZ = -50;

  var XDelta = 1;
  var ZDelta = 1;
  var YMax = 30;
  var YMin = -30;
  var YDiff = YMax - YMin;

  var curX = startX;
  var curZ = startZ;
  var positionArray = [];
  var curIndex = 0;

  var uvCoords = [
    [0, 0],
    [1, 0],
    [1, 1],
    [0, 1]
  ];

  for(var i = 0; i < g_heightMapCanvas.width; i++) {
    for (var j = 0; j < g_heightMapCanvas.height; j++) {
      color = getPixelColor(i, j, g_heightMapImageData);
      avgColor = ((color[0] + color[1] + color[2]) / 3) / 255;
	  positionArray[curIndex] = [];
      positionArray[curIndex][0] = curX;
      positionArray[curIndex][1] = YMin + (YDiff * avgColor);
      positionArray[curIndex][2] = curZ;
      curIndex++;
      curZ = curZ + ZDelta;
    }
    curX = curX + XDelta;
    curZ = startZ;
  }

  var vertexOffset = 0;
  var curPosIndex = 0;
  for(var i = 0; i < g_heightMapCanvas.width - 1; i++) {
    for (var j = 0; j < g_heightMapCanvas.height - 1; j++) {
	  var p1 = positionArray[curPosIndex];
	  var p2 = positionArray[curPosIndex + g_heightMapCanvas.height];
	  var p3 = positionArray[curPosIndex + g_heightMapCanvas.height + 1];
	  var p4 = positionArray[curPosIndex + 1];

      positionStream.addElementVector(p2);
	  texCoordStream.addElementVector(uvCoords[1]);

      positionStream.addElementVector(p1);
	  texCoordStream.addElementVector(uvCoords[0]);

      positionStream.addElementVector(p4);
	  texCoordStream.addElementVector(uvCoords[3]);

	  var v1 = g_math.subVector(p1, p2);
	  var v2 = g_math.subVector(p4, p2);
	  var normal = g_math.cross(v1, v2);
	  normal = g_math.normalize(normal);

	  normalStream.addElementVector(normal);
	  normalStream.addElementVector(normal);
	  normalStream.addElementVector(normal);

      vertexInfo.addTriangle(vertexOffset, vertexOffset + 1, vertexOffset + 2);
      vertexOffset = vertexOffset + 3;

      positionStream.addElementVector(p4);
	  texCoordStream.addElementVector(uvCoords[3]);

      positionStream.addElementVector(p3);
	  texCoordStream.addElementVector(uvCoords[2]);

      positionStream.addElementVector(p2);
	  texCoordStream.addElementVector(uvCoords[1]);

	  v1 = g_math.subVector(p3, p4);
	  v2 = g_math.subVector(p2, p4);
	  normal = g_math.cross(v1, v2);
	  normal = g_math.normalize(normal);

	  normalStream.addElementVector(normal);
	  normalStream.addElementVector(normal);
	  normalStream.addElementVector(normal);

      vertexInfo.addTriangle(vertexOffset , vertexOffset + 1, vertexOffset + 2);
      vertexOffset = vertexOffset + 3;

      curPosIndex++;
    }
  }
  return vertexInfo.createShape(g_pack, material);

} 

function getPixelColor(x, y, imageData) {
  var index=(x * 4) * imageData.width + (y * 4);
  var colArray = [];

  colArray[0] = imageData.data[index];
  colArray[1] = imageData.data[index+1];
  colArray[2] = imageData.data[index+2];
  colArray[3] = imageData.data[index+3];
  return colArray;
}
function loadHeightMap(image)
{
  g_heightMapCanvas=document.createElement("canvas");
  g_heightMapCanvasContext=g_heightMapCanvas.getContext("2d");

  g_heightMapCanvas.width= image.width;
  g_heightMapCanvas.height=image.height;
  g_heightMapCanvasContext.drawImage(image,0,0);

  g_heightMapImageData = g_heightMapCanvasContext.getImageData(0,0, image.width, image.height);

  g_heightMapLoaded = true;
}

function loadLandscape()
{
  // Create a Material for the mesh.
  var landscapeMaterial = createPhongMaterial([1, 1, 1, 1]);

  o3djs.io.loadTexture(g_pack, 'tutorial20/image.png',
      function(texture, exception) {
        if (exception) {
          g_sampler.texture = null;
        } else {
          g_sampler.texture = texture;
		  // Create the Shape for the landscape mesh and assign its material.
		  var landscapeShape = createLandscape(landscapeMaterial);

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

		  // Parent the landscape's transform to the client root.
		  g_landscapeTransform.parent = g_3dRoot;

		  // Generate the draw elements for the landscape shape.
		  landscapeShape.createDrawElements(g_pack, null);
		}
      });  

}

/**
 * Creates a phong material based on the given single color.
 * @param {Array} baseColor An array with 4 entries, the R,G,B, and A components
 *   of a color.
 * @return {Material} A phong material whose overall pigment is baseColor.
 */
function createPhongMaterial(baseColor) {
   // Load effect
  var effect = g_pack.createObject('Effect');
  var shaderString = 'shaders/phongtexmultilight.shader';
  o3djs.effect.loadEffect(effect, shaderString);

  // Create a new, empty Material object.
  var material = g_pack.createObject('Material');

  material.drawList = g_viewInfo.performanceDrawList;

  material.effect = effect;
  effect.createUniformParameters(material);

  // Assign parameters to the phong material.
  material.getParam('emissive').value = [0, 0, 0, 1];
  material.getParam('ambient').value = g_math.mulScalarVector(0.1, baseColor);
  material.getParam('diffuse').value = g_math.mulScalarVector(0.9, baseColor);
  material.getParam('specular').value = [.2, .2, .2, 1];
  material.getParam('shininess').value = 20;
  material.getParam('lightIntensity1').value = g_lightColor1;
  material.getParam('lightWorldPos1').value = g_lightPosition1;
  material.getParam('ambientIntensity').value = [0.2, 0.2, 0.2, 1];

  material.getParam('colorMult').value = [1, 1, 1, 1];

  material.getParam('lightIntensity2').value = g_lightColor2;
  material.getParam('lightWorldPos2').value = g_lightPosition2;

  var samplerParam = material.getParam('texSampler0');
  g_sampler = g_pack.createObject('Sampler');
  g_sampler.minFilter = g_o3d.Sampler.ANISOTROPIC;
  g_sampler.maxAnisotropy = 4;
  samplerParam.value = g_sampler;

  return material;
}

//Event handler for the mousedown event
function mouseDown(e) {
  if (e.button == 0) {
      var worldRay = o3djs.picking.clientPositionToWorldRay(
          e.x,
          e.y,
          g_viewInfo.drawContext,
          g_client.width,
          g_client.height);
      g_treeInfo.update();
      g_pickedInfo = g_treeInfo.pick(worldRay);
      if (g_pickedInfo) {
        g_spinningObject = true;

      }
   } else {
    g_lastRot = g_thisRot;
    g_aball.click([e.x, e.y]);
    g_dragging = true;
   }
   g_mouseLocked = !g_mouseLocked
}

//Event handler for the mousemove event
function mouseMove(e) {
  g_lastMouseX = g_mouseX;
  g_lastMouseY = g_mouseY;
  g_mouseX = e.x;
  g_mouseY = e.y;

  g_mouseXDelta = g_mouseX - g_lastMouseX;
  g_mouseYDelta = g_mouseY - g_lastMouseY;

  if (g_mouseLocked) {
    var viewDir = g_math.subVector(g_camera.target, g_camera.eye);

    var rotatedViewDir = [];
    rotatedViewDir[0] = (Math.cos(g_mouseXDelta * g_rotationDelta) * viewDir[0]) - (Math.sin(g_mouseXDelta * g_rotationDelta) * viewDir[2]);
    rotatedViewDir[1] = viewDir[1];
    rotatedViewDir[2] = (Math.cos(g_mouseXDelta * g_rotationDelta) * viewDir[2]) + (Math.sin(g_mouseXDelta * g_rotationDelta) * viewDir[0]);
    viewDir = rotatedViewDir;
    rotatedViewDir[0] = viewDir[0];
    rotatedViewDir[1] = (Math.cos(g_mouseYDelta * g_rotationDelta * -1) * viewDir[1]) - (Math.sin(g_mouseYDelta * g_rotationDelta * -1) * viewDir[2]);
    rotatedViewDir[2] = (Math.cos(g_mouseYDelta * g_rotationDelta * -1) * viewDir[2]) + (Math.sin(g_mouseYDelta * g_rotationDelta * -1) * viewDir[1]);
    g_lookingDir = rotatedViewDir;
    g_camera.target = g_math.addVector(rotatedViewDir, g_camera.eye);
    g_viewInfo.drawContext.view = g_math.matrix4.lookAt(g_camera.eye,
                                                     g_camera.target,
                                                     [0, 1, 0]);

  }

}

//Event handler for the mouseup event
function mouseUp(e) {
  g_dragging = false;
  g_spinningObject = false;
}

//Even handler for the scroll button
function scrollMe(e) {
  if (e.deltaY) {
	var view = g_math.subVector(g_camera.eye, g_camera.target);
    view = g_math.mulScalarVector((e.deltaY < 0 ? 11 : 13) / 12, view);
	g_camera.eye = g_math.addVector(view, g_camera.target);
    g_viewInfo.drawContext.view = g_math.matrix4.lookAt(g_camera.eye,
                                                       g_camera.target,
                                                       [0, 1, 0]);
 }
}         

// Updates the transform info for our scene
function updateInfo() {
  if (!g_treeInfo) {
    g_treeInfo = o3djs.picking.createTransformInfo(g_3dRoot, null);
  }
  g_treeInfo.update();
}

function drawText(str) {
  g_textCanvas.canvas.clear([0.5, 0.5, 0.5, 0.5]);

  // Reuse the global paint object
  var paint = g_paint;
  paint.color = [1, 1, 1, 1];
  paint.textSize = 12;
  paint.textTypeface = 'Comic Sans MS';
  paint.textAlign = g_o3d.CanvasPaint.LEFT;
  paint.shader = null;
  g_textCanvas.canvas.drawText(str, 10, 30, paint);

  g_textCanvas.updateTexture();
}         

/**
 * This method gets called every time O3D renders a frame.  Here's
 * where we update the landscape's transform to make it spin.
 * @param {o3d.RenderEvent} renderEvent The render event object that
 * gives us the elapsed time since the last time a frame was rendered.
 */
function renderCallback(renderEvent) {
  g_clock += renderEvent.elapsedTime * g_timeMult;
  g_camera.eye
  drawText("(" + (Math.round(g_camera.eye[0] * 100) / 100) + "," + (Math.round(g_camera.eye[1] * 100) / 100) +"," + (Math.round(g_camera.eye[2] * 100) / 100) + ")   " + "(" + (Math.round(g_lookingDir[0] * 100) / 100) + "," + (Math.round(g_lookingDir[1] * 100) / 100) +"," + (Math.round(g_lookingDir[2] * 100) / 100) + ")");
  if (g_spinningObject) {

     var pickTrans = g_pickedInfo.shapeInfo.parent.transform;
     pickTrans.rotateX(0.05);
     pickTrans.rotateY(0.05);
  }
  updateInfo();
}

/**
 * Function performing the rotate action in response to a key-press.
 * Rotates the scene based on key pressed. (w ,s, a, d). Note that the
 * x,y-axis referenced here are relative to the current view of scene.
 * @param {keyPressed} The letter pressed, in lower case.
 * @param {delta} The angle by which the scene should be rotated.
 * @return true if an action was taken.
 */
function keyPressedAction(keyPressed, delta) {
  var actionTaken = false;
  switch(keyPressed) {
    case 'a':
		var eyeOriginal = g_camera.eye;
		var targetOriginal = g_camera.target;
		var viewEye = g_math.subVector(g_camera.eye, g_camera.target);
		var viewTarget = g_math.subVector(g_camera.target, g_camera.eye);
		viewEye = g_math.addVector([g_translationDelta * -1, 0, 0], viewEye);
		viewTarget = g_math.addVector([g_translationDelta * -1, 0, 0], viewTarget);
		g_camera.eye = g_math.addVector(viewEye, targetOriginal);
		g_camera.target = g_math.addVector(viewTarget, eyeOriginal);

		g_viewInfo.drawContext.view = g_math.matrix4.lookAt(g_camera.eye,
                                                       g_camera.target,
                                                       [0, 1, 0]);
      actionTaken = true;
      break;
    case 'd':
		var eyeOriginal = g_camera.eye;
		var targetOriginal = g_camera.target;
		var viewEye = g_math.subVector(g_camera.eye, g_camera.target);
		var viewTarget = g_math.subVector(g_camera.target, g_camera.eye);
		viewEye = g_math.addVector([g_translationDelta, 0, 0], viewEye);
		viewTarget = g_math.addVector([g_translationDelta, 0, 0], viewTarget);
		g_camera.eye = g_math.addVector(viewEye, targetOriginal);
		g_camera.target = g_math.addVector(viewTarget, eyeOriginal);

		g_viewInfo.drawContext.view = g_math.matrix4.lookAt(g_camera.eye,
                                                       g_camera.target,
                                                       [0, 1, 0]);
      actionTaken = true;
      break;
    case 'w':
		var view = g_math.subVector(g_camera.eye, g_camera.target);
		view = g_math.mulScalarVector( 11 / 12, view);
		g_camera.eye = g_math.addVector(view, g_camera.target);
		g_viewInfo.drawContext.view = g_math.matrix4.lookAt(g_camera.eye,
                                                       g_camera.target,
                                                       [0, 1, 0]);
      actionTaken = true;
      break;
    case 's':
		var view = g_math.subVector(g_camera.eye, g_camera.target);
		view = g_math.mulScalarVector( 13 / 12, view);
		g_camera.eye = g_math.addVector(view, g_camera.target);
		g_viewInfo.drawContext.view = g_math.matrix4.lookAt(g_camera.eye,
                                                       g_camera.target,
                                                       [0, 1, 0]);
      actionTaken = true;
      break;
  }
  return actionTaken;
}

/**
 * Callback for the keypress event.
 * Invokes the action to be performed for the key pressed.
 * @param {event} keyPress event passed to us by javascript.
 */
function keyPressedCallback(event) {
  event = event || window.event;

  // Ignore accelerator key messages.
  if (event.metaKey)
    return;

  var keyChar =String.fromCharCode(o3djs.event.getEventKeyChar(event));
  // Just in case they have capslock on.
  keyChar = keyChar.toLowerCase();

  if (keyPressedAction(keyChar, g_keyPressDelta)) {
    o3djs.event.cancel(event);
  }
}         

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

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

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

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

  //Create the arcball which is used for the rotation
  g_aball = o3djs.arcball.create(400, 400);

  //Initialise rotation matrixes
  g_lastRot = g_math.matrix4.identity();
  g_thisRot = g_math.matrix4.identity();

  // Create 2 root transforms, one for the 3d parts and 2d parts.
  // This is not strictly neccassary but it is helpful.
  g_3dRoot = g_pack.createObject('Transform');
  g_hudRoot = g_pack.createObject('Transform');
  g_backgroundRoot = g_pack.createObject('Transform');

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

  // Create a second view for the hud.
  g_hudViewInfo = o3djs.rendergraph.createBasicView(
       g_pack,
       g_hudRoot,
       g_client.renderGraphRoot);

  // Turn off clearing the color for the hud since that would erase the
  // 3d parts but leave clearing the depth and stencil so the HUD is
  //  unaffected by anything done by the 3d parts.
  g_hudViewInfo.clearBuffer.clearColorFlag = false;
  g_viewInfo.clearBuffer.clearColorFlag = false;

  // Create a view for the background.
  g_backgroundViewInfo = o3djs.rendergraph.createBasicView(
       g_pack,
       g_backgroundRoot,
       g_client.renderGraphRoot);

  // Make sure the background gets drawn first
  g_viewInfo.root.priority = g_backgroundViewInfo.root.priority + 1;
  // Make sure the hud gets drawn after the 3d stuff
  g_hudViewInfo.root.priority = g_viewInfo.root.priority + 1;

  //g_backgroundViewInfo.root.priority + 5;
  // Set up a perspective view
  g_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 landscape is located.
  g_viewInfo.drawContext.view = g_math.matrix4.lookAt(g_camera.eye, //eye
                                            g_camera.target,  // target
                                            [0, 1, 0]); // up

  //Set up the 2d orthographic view
  g_hudViewInfo.drawContext.projection = g_math.matrix4.orthographic(
      0 + 0.5,
      g_client.width + 0.5,
      g_client.height + 0.5,
      0 + 0.5,
      0.001,
      1000);

  g_hudViewInfo.drawContext.view = g_math.matrix4.lookAt(
      [0, 0, 1],   // eye
      [0, 0, 0],   // target
      [0, 1, 0]);  // up                                                     

  //Set up the 2d orthographic view
  g_backgroundViewInfo.drawContext.projection = g_math.matrix4.orthographic(
      0 + 0.5,
      g_client.width + 0.5,
      g_client.height + 0.5,
      0 + 0.5,
      0.001,
      1000);

  g_backgroundViewInfo.drawContext.view = g_math.matrix4.lookAt(
      [0, 0, 1],   // eye
      [0, 0, 0],   // target
      [0, 1, 0]);  // up     

  // Create the global paint object that's used by draw operations.
  g_paint = g_pack.createObject('CanvasPaint');

  // Creates an instance of the canvas utilities library.
  g_canvasLib = o3djs.canvas.create(g_pack, g_hudRoot, g_hudViewInfo);

  // Create a canvas that will be used to display the text.
  g_textCanvas = g_canvasLib.createXYQuad(0, 0, 0, 400, 50, true);

  g_backgroundCanvasLib = o3djs.canvas.create(g_pack, g_backgroundRoot, g_backgroundViewInfo);
  g_backgroundCanvas = g_backgroundCanvasLib.createXYQuad(0, 0, 0, g_client.width, g_client.height, true);

  g_backgroundCanvas.canvas.clear([0, 0, 0, 1]);

  o3djs.io.loadTexture(g_pack, 'tutorial20/bg.jpg', function(texture, exception) {
    if (exception) {
      alert(exception);
    } else {
      if (g_backgroundTexture) {
        g_pack.removeObject(g_backgroundTexture);
      }

      g_backgroundTexture = texture;
      g_backgroundCanvas.canvas.drawBitmap(g_backgroundTexture, 0, g_client.width);
      g_backgroundCanvas.updateTexture();
    }
  });  

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

  //Set up a callback to interpret keypresses
  window.document.onkeypress = keyPressedCallback;

  //Set up mouse events
  o3djs.event.addEventListener(g_o3dElement, 'mousedown', mouseDown);
  o3djs.event.addEventListener(g_o3dElement, 'mousemove', mouseMove);
  o3djs.event.addEventListener(g_o3dElement, 'mouseup', mouseUp);
  o3djs.event.addEventListener(g_o3dElement, 'wheel', scrollMe);
  g_initRun = true;
}

/**
 * Removes callbacks so they aren't called after the page has unloaded.
 */
function uninit() {
  if (g_client) {
    g_client.cleanup();
  }
}
Share