Up until now, the cube we have drawn was created by a custom object. There is an easier way of creating basic shapes. This is using primitives.
What primitives are, essentially, is a set of pre-configured objects that you can create instances of to display on your screen.
I have removed all code from the previous tutorial that created the cube, and instead have created a new function that draws a cube, accompanied by a few new friends. First the code creates a fewprimitives. Since objects are created at the origin, we need to translate the object to a spot we want them or else they will all be on top of each other, therefore after we create the objects, we apply a translation translation to the objects, and add them to the 3D root, so that they will be displayed.
function createShapes(material) {
var cube = o3djs.primitives.createCube(
g_pack,
material,
Math.sqrt(2)); // The length of each side of the cube.
var sphere = o3djs.primitives.createSphere(
g_pack,
material,
1.0, // Radius of the sphere.
30, // Number of meridians.
20); // Number of parallels.
var cylinder = o3djs.primitives.createCylinder(
g_pack,
material,
0.5, // Radius.
1.5, // Depth.
20, // Number of radial subdivisions.
20); // Number of vertical subdivisions.
var plane = o3djs.primitives.createPlane(
g_pack,
material,
1, // Width.
1.618, // Depth.
3, // Horizontal subdivisions.
3); // Vertical subdivisions.
// Make a polygon to extrude for the prism.
var polygon = [];
var n = 10;
for (var i = 0; i < n; ++i) {
var theta = 2.0 * i * Math.PI / n;
var radius = (i % 2) ? 1 : 0.382;
polygon.push([radius * Math.cos(theta), radius * Math.sin(theta)]);
}
var prism = o3djs.primitives.createPrism(
g_pack,
material,
polygon, // The profile polygon to be extruded.
1); // The depth of the extrusion.
var disc = o3djs.primitives.createDisc(
g_pack,
material,
1, // Radius.
7, // Divisions.
2, // Stacks (optional).
0, // Start Stack (optional).
2); // Stack Power (optional).
// Add the shapes to the transforms.
var transformTable = [
{shape: cube, translation: [-2, 1, 0]},
{shape: sphere, translation: [0, 1, 0]},
{shape: cylinder, translation: [2, 1, 0]},
{shape: plane, translation: [-2, -1, 0]},
{shape: prism, translation: [0, -1, 0]},
{shape: disc, translation: [2, -1, 0]}
];
for (var i = 0; i < transformTable.length; i++) {
var transform = g_pack.createObject('Transform');
transform.addShape(transformTable[i].shape);
transform.translate(transformTable[i].translation);
transform.parent = g_3dRoot;
}
}
When you run the code, you may notice that the objects are all a rather boring red, with no shading or lighting, which is something that I will address in the next tutorial…
Here is the full listing of 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');
// 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_cubeTransform;
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_camera = {
eye: [0, 0, 10],
target: [0, 0, 0]
};
function startDragging(e) {
g_lastRot = g_thisRot;
g_aball.click([e.x, e.y]);
g_dragging = true;
}
function drag(e) {
if (g_dragging) {
var rotationQuat = g_aball.drag([e.x, e.y]);
var rot_mat = g_quaternions.quaternionToRotation(rotationQuat);
g_thisRot = g_math.matrix4.mul(g_lastRot, rot_mat);
var m = g_3dRoot.localMatrix;
g_math.matrix4.setUpper3x3(m, g_thisRot);
g_3dRoot.localMatrix = m;
}
}
function stopDragging(e) {
g_dragging = false;
}
function scrollMe(e) {
if (e.deltaY) {
g_camera.eye =
g_math.mulScalarVector((e.deltaY < 0 ? 11 : 13) / 12, g_camera.eye);
g_viewInfo.drawContext.view = g_math.matrix4.lookAt(g_camera.eye,
g_camera.target,
[0, 1, 0]);
}
}
function drawText(str) {
// Clear to completely transparent.
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 cube'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;
drawText("Hello world - " + (Math.round(g_clock * 100) / 100) + "s");
}
/**
* 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':
g_3dRoot.localMatrix =
g_math.matrix4.mul(g_3dRoot.localMatrix,
g_math.matrix4.rotationY(-delta));
actionTaken = true;
break;
case 'd':
g_3dRoot.localMatrix =
g_math.matrix4.mul(g_3dRoot.localMatrix,
g_math.matrix4.rotationY(delta));
actionTaken = true;
break;
case 'w':
g_3dRoot.localMatrix =
g_math.matrix4.mul(g_3dRoot.localMatrix,
g_math.matrix4.rotationX(-delta));
actionTaken = true;
break;
case 's':
g_3dRoot.localMatrix =
g_math.matrix4.mul(g_3dRoot.localMatrix,
g_math.matrix4.rotationX(delta));
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);
}
}
function createShapes(material) {
var cube = o3djs.primitives.createCube(
g_pack,
material,
Math.sqrt(2)); // The length of each side of the cube.
var sphere = o3djs.primitives.createSphere(
g_pack,
material,
1.0, // Radius of the sphere.
30, // Number of meridians.
20); // Number of parallels.
var cylinder = o3djs.primitives.createCylinder(
g_pack,
material,
0.5, // Radius.
1.5, // Depth.
20, // Number of radial subdivisions.
20); // Number of vertical subdivisions.
var plane = o3djs.primitives.createPlane(
g_pack,
material,
1, // Width.
1.618, // Depth.
3, // Horizontal subdivisions.
3); // Vertical subdivisions.
// Make a polygon to extrude for the prism.
var polygon = [];
var n = 10;
for (var i = 0; i < n; ++i) {
var theta = 2.0 * i * Math.PI / n;
var radius = (i % 2) ? 1 : 0.382;
polygon.push([radius * Math.cos(theta), radius * Math.sin(theta)]);
}
var prism = o3djs.primitives.createPrism(
g_pack,
material,
polygon, // The profile polygon to be extruded.
1); // The depth of the extrusion.
var disc = o3djs.primitives.createDisc(
g_pack,
material,
1, // Radius.
7, // Divisions.
2, // Stacks (optional).
0, // Start Stack (optional).
2); // Stack Power (optional).
// Add the shapes to the transforms.
var transformTable = [
{shape: cube, translation: [-2, 1, 0]},
{shape: sphere, translation: [0, 1, 0]},
{shape: cylinder, translation: [2, 1, 0]},
{shape: plane, translation: [-2, -1, 0]},
{shape: prism, translation: [0, -1, 0]},
{shape: disc, translation: [2, -1, 0]}
];
for (var i = 0; i < transformTable.length; i++) {
var transform = g_pack.createObject('Transform');
transform.addShape(transformTable[i].shape);
transform.translate(transformTable[i].translation);
transform.parent = g_3dRoot;
}
}
/**
* 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.
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(300, 300);
//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');
// Create the render graph for a view.
g_viewInfo = o3djs.rendergraph.createBasicView(
g_pack,
g_3dRoot,
g_client.renderGraphRoot);
// Set the background color to black.
g_viewInfo.clearBuffer.clearColor = [0, 0, 0, 1];
// Create a second view for the hud.
g_hudViewInfo = o3djs.rendergraph.createBasicView(
g_pack,
g_hudRoot,
g_client.renderGraphRoot);
// Make sure the hud gets drawn after the 3d stuff
g_hudViewInfo.root.priority = g_viewInfo.root.priority + 1;
// 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;
// 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 cube 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
// Load effect
var redEffect = g_pack.createObject('Effect');
var shaderString = 'shaders/solidred.shader';
o3djs.effect.loadEffect(redEffect, shaderString);
// Create a Material for the mesh.
var redMaterial = g_pack.createObject('Material');
// Set the material's drawList.
redMaterial.drawList = g_viewInfo.performanceDrawList;
// Apply our effect to this material. The effect tells the 3D
// hardware which shaders to use.
redMaterial.effect = redEffect;
createShapes(redMaterial);
// 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(70, 70, 0, 100, 50, true);
// 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', startDragging);
o3djs.event.addEventListener(g_o3dElement, 'mousemove', drag);
o3djs.event.addEventListener(g_o3dElement, 'mouseup', stopDragging);
o3djs.event.addEventListener(g_o3dElement, 'wheel', scrollMe);
}
/**
* Removes callbacks so they aren't called after the page has unloaded.
*/
function uninit() {
if (g_client) {
g_client.cleanup();
}
}
Related posts:
- O3D Tutorial 11: Calculating the frame rate One of the most important considerations when doing 3D animations...
- O3D Tutorial 13: Setting a background image Now that we have covered textures, I think it is...
- O3D Tutorial 10: Interacting with an object Displaying a 3D scene is all good and well. We...
- O3D Tutorial 14: Loading a scene O3D makes it very easy to load scenes. A scene,...
- O3D Tutorial 12: Textures Textures are a vital aspect creating realistic looking 3d objects....
Related posts brought to you by Yet Another Related Posts Plugin.
Serge Meunier is a software developer living in Cape Town, South Africa. He loves programming, fencing, philosophy, feeding his internet addiction, and, of course, dogs.
Comments