topical media & game development
graphic-o3d-samples-o3djs-util.js / js
/*
* Copyright 2009, Google Inc.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the
* distribution.
* * Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
@fileoverview This file contains various utility functions for o3d. It
puts them in the "util" module on the o3djs object.
o3djs.provide('o3djs.util');
o3djs.require('o3djs.io');
o3djs.require('o3djs.event');
o3djs.require('o3djs.error');
A Module with various utilities.
@namespace
o3djs.util = o3djs.util || {};
{
The name of the o3d plugin. Used to find the plugin when checking
for its version.
@type {string}
o3djs.util.PLUGIN_NAME = 'O3D Plugin';
The version of the plugin needed to use this version of the javascript
utility libraries.
@type {string}
o3djs.util.REQUIRED_VERSION = '0.1.20.0';
A URL at which to download the client.
@type {string}
o3djs.util.PLUGIN_DOWNLOAD_URL = 'http://tools.google.com/dlpage/o3d';
This implements a JavaScript version of currying. Currying allows you to
take a function and fix its initial arguments, resulting in a function
expecting only the remaining arguments when it is invoked. For example:
<pre>
function add(a, b) {
return a + b;
}
var increment = o3djs.util.curry(add, 1);
var result = increment(10);
</pre>
Now result equals 11.
parameter: {!function} The function to curry.
returns: {!function} The curried function.
o3djs.util.curry = function(func) {
var outerArgs = [];
for (var i = 1; i < arguments.length; ++i) {
outerArgs.push(arguments[i]);
}
return function() {
var innerArgs = outerArgs.slice();
for (var i = 0; i < arguments.length; ++i) {
innerArgs.push(arguments[i]);
}
return func.apply(this, innerArgs);
}
}
Gets the URI in which the current page is located, omitting the file name.
returns: {string} The base URI of the page. If the page is
"http://some.com/folder/somepage.html" returns
"http://some.com/folder/".
o3djs.util.getCurrentURI = function() {
var path = window.location.href;
var index = path.lastIndexOf('/');
return path.substring(0, index + 1);
};
Given a URI that is relative to the current page, returns the absolute
URI.
parameter: {string} uri URI relative to the current page.
returns: {string} Absolute uri. If the page is
"http://some.com/folder/sompage.html" and you pass in
"images/someimage.jpg" will return
"http://some.com/folder/images/someimage.jpg".
o3djs.util.getAbsoluteURI = function(uri) {
return o3djs.util.getCurrentURI() + uri;
};
Searches an array for a specific value.
parameter: {Array} array Array to search.
parameter: {Object} value Value to search for.
returns: {boolean} True if value is in array.
o3djs.util.arrayContains = function(array, value) {
for (var i = 0; i < array.length; i++) {
if (array[i] == value) {
return true;
}
}
return false;
};
Searches for all transforms with a "o3d.tags" ParamString
that contains specific tag keywords assuming comma separated
words.
parameter: {Transform} treeRoot Root of tree to search for tags.
parameter: {string} searchTags Tags to look for. eg "camera", "ogre,dragon".
returns: {Array} Array of transforms.
o3djs.util.getTransformsInTreeByTags = function(treeRoot, searchTags) {
var splitTags = searchTags.split(',');
var transforms = treeRoot.getTransformsInTree();
var found = [];
for (var n = 0; n < transforms.length; n++) {
var tagParam = transforms[n].getParam('collada.tags');
if (tagParam) {
var tags = tagParam.value.split(',');
for (var t = 0; t < tags.length; t++) {
if (o3djs.util.arrayContains(splitTags, tags[t])) {
found[found.length] = transforms[n];
break;
}
}
}
}
return found;
};
Finds transforms in the tree by prefix.
parameter: {Transform} treeRoot Root of tree to search.
parameter: {string} prefix Prefix to look for.
returns: {Array} Array of transforms matching prefix.
o3djs.util.getTransformsInTreeByPrefix = function(treeRoot, prefix) {
var found = [];
var transforms = treeRoot.getTransformsInTree();
for (var ii = 0; ii < transforms.length; ii++) {
var transform = transforms[ii];
if (transform.name.indexOf(prefix) == 0) {
found[found.length] = transform;
}
}
return found;
};
Finds the bounding box of all primitives in the tree, in the local space of
the tree root. This will use existing bounding boxes on transforms and
elements, but not create new ones.
parameter: {Transform} treeRoot Root of tree to search.
returns: {BoundingBox} The boundinding box of the tree.
o3djs.util.getBoundingBoxOfTree = function(treeRoot) {
// If we already have a bounding box, use that one.
var box = treeRoot.boundingBox;
if (box.valid) {
return box;
}
var o3d = o3djs.base.o3d;
// Otherwise, create it as the union of all the children bounding boxes and
// all the shape bounding boxes.
var transforms = treeRoot.children;
for (var i = 0; i < transforms.length; ++i) {
var transform = transforms[i];
var childBox = o3djs.util.getBoundingBoxOfTree(transform);
if (childBox.valid) {
// transform by the child local matrix.
childBox = childBox.mul(transform.localMatrix);
if (box.valid) {
box = box.add(childBox);
} else {
box = childBox;
}
}
}
var shapes = treeRoot.shapes;
for (var i = 0; i < shapes.length; ++i) {
var elements = shapes[i].elements;
for (var j = 0; j < elements.length; ++j) {
var elementBox = elements[j].boundingBox;
if (!elementBox.valid) {
elementBox = elements[j].getBoundingBox(0);
}
if (box.valid) {
box = box.add(elementBox);
} else {
box = elementBox;
}
}
}
return box;
};
Returns the smallest power of 2 that is larger than or equal to size.
parameter: {number} size Size to get power of 2 for.
returns: {number} smallest power of 2 that is larger than or equal to size.
o3djs.util.getPowerOfTwoSize = function(size) {
var powerOfTwo = 1;
while (size) {
size = size >> 1;
powerOfTwo = powerOfTwo << 1;
}
return powerOfTwo;
};
Gets the version of the installed plugin.
returns: {?string} version string in 'major.minor.revision.build' format.
If the plugin does not exist returns null.
o3djs.util.getPluginVersion = function() {
var version = null;
var description = null;
if (navigator.plugins != null && navigator.plugins.length > 0) {
var plugin = navigator.plugins[o3djs.util.PLUGIN_NAME];
if (plugin) {
description = plugin.description;
}
} else if (o3djs.base.IsMSIE()) {
try {
var activeXObject = new ActiveXObject('o3d_host.O3DHostControl');
description = activeXObject.description;
} catch (e) {
// O3D plugin was not found.
}
}
if (description) {
var re = /.*version:(\d+)\.(\d+)\.(\d+)\.(\d+).*/;
// Parse the version out of the description.
var parts = re.exec(description);
if (parts && parts.length == 5) {
// make sure the format is #.#.#.# no whitespace, no trailing comments
version = '' + parseInt(parts[1]) + '.' +
parseInt(parts[2]) + '.' +
parseInt(parts[3]) + '.' +
parseInt(parts[4]);
}
}
return version;
};
Checks if the required version of the plugin in available.
parameter: {string} requiredVersion version string in
"major.minor.revision.build" format. You can leave out any non-important
numbers for example "3" = require major version 3, "2.4" = require major
version 2, minor version 4.
returns: {boolean} True if the required version is available.
o3djs.util.requiredVersionAvailable = function(requiredVersion) {
var version = o3djs.util.getPluginVersion();
if (!version) {
return false;
}
var haveParts = version.split('.');
var requiredParts = requiredVersion.split('.');
if (requiredParts.length > 4) {
throw RangeError('requiredVersion has more than 4 parts!');
}
for (var pp = 0; pp < requiredParts.length; ++pp) {
var have = parseInt(haveParts[pp]);
var required = parseInt(requiredParts[pp]);
if (have < required) {
return false;
}
if (have > required) {
return true;
}
}
return true;
};
Offers the user the option to download the plugin.
Finds all divs with the id "^o3d" and inserts a message and link
inside to download the plugin. If no areas exist OR if none of them are
large enough for the message then displays an alert.
parameter: {string} opt_id The id to look for. This can be a regular
expression. The default is "^o3d".
parameter: {string} opt_tag The type of tag to look for. The default is "div".
o3djs.util.offerPlugin = function(opt_id, opt_tag) {
var tag = opt_tag || 'div';
var id = opt_id || '^o3d';
var havePlugin = o3djs.util.requiredVersionAvailable('');
var elements = document.getElementsByTagName(tag);
var addedMessage = false;
// TODO: This needs to be localized OR we could insert a html like
// <script src="http://google.com/o3d_plugin_dl"></script>
// in which case google could serve the message localized and update the
// link.
var subMessage =
(havePlugin ?
'This page requires a newer version of the O3D plugin.' :
'This page requires the O3D plugin to be installed.');
var message =
'<div style="background: lightblue; width: 100%; height: 100%; ' +
'text-align:center;">' +
'<br/><br/>' + subMessage + '<br/>' +
'<a href="' + o3djs.util.PLUGIN_DOWNLOAD_URL +
'">Click here to download.</a>' +
'</div>'
for (var ee = 0; ee < elements.length; ++ee) {
var element = elements[ee];
if (element.id && element.id.match(id)) {
if (element.clientWidth >= 200 &&
element.clientHeight >= 200 &&
element.style.display.toLowerCase() != 'none' &&
element.style.visibility.toLowerCase() != 'hidden') {
addedMessage = true;
element.innerHTML = message;
}
}
}
if (!addedMessage) {
if (confirm(subMessage + '\n\nClick OK to download.')) {
window.location = o3djs.util.PLUGIN_DOWNLOAD_URL;
}
}
};
Tells the user their graphics card is not able to run the plugin or is out
of resources etc.
Finds all divs with the id "^o3d" and inserts a message. If no areas
exist OR if none of them are large enough for the message then displays an
alert.
parameter: {!o3d.Renderer.InitStatus} initStatus The initializaion status of
the renderer.
parameter: {!o3d} o3d an O3D object to lookup constants with.
parameter: {string} opt_id The id to look for. This can be a regular
expression. The default is "^o3d".
parameter: {string} opt_tag The type of tag to look for. The default is "div".
o3djs.util.informNoGraphics = function(initStatus, o3d, opt_id, opt_tag) {
var tag = opt_tag || 'div';
var id = opt_id || '^o3d';
var elements = document.getElementsByTagName(tag);
var addedMessage = false;
var subMessage;
var message;
var alertMessage = '';
var alertFunction = function() { };
// TODO: This needs to be localized OR we could insert a html like
// <script src="http://google.com/o3d_plugin_dl"></script>
// in which case google could serve the message localized and update the
// link.
if (initStatus == o3d.Renderer.GPU_NOT_UP_TO_SPEC) {
subMessage =
'We are terribly sorry but it appears your graphics card is not ' +
'able to run o3d. We are working on a solution.';
message =
'<div style="background: lightgray; width: 100%; height: 100%; ' +
'text-align: center;">' +
'<br/><br/>' + subMessage +
'<br/><br/><a href="' + o3djs.util.PLUGIN_DOWNLOAD_URL +
'">Click Here to go the O3D website</a>' +
'</div>';
alertMessage = '\n\nClick OK to go to the o3d website.';
alertFunction = function() {
window.location = o3djs.util.PLUGIN_DOWNLOAD_URL;
};
} else if (initStatus == o3d.Renderer.OUT_OF_RESOURCES) {
subMessage =
'Your graphics system appears to be out of resources. Try closing ' +
'some applications and then refreshing this page.';
message =
'<div style="background: lightgray; width: 100%; height: 100%; ' +
'text-align: center;">' +
'<br/><br/>' + subMessage +
'</div>';
} else {
subMessage =
'A unknown error has prevented O3D from starting. Try downloading' +
'new drivers or checking for OS updates.';
message =
'<div style="background: lightgray; width: 100%; height: 100%; ' +
'text-align: center;">' +
'<br/><br/>' + subMessage +
'</div>';
}
for (var ee = 0; ee < elements.length; ++ee) {
var element = elements[ee];
if (element.id && element.id.match(id)) {
if (element.clientWidth >= 200 &&
element.clientHeight >= 200 &&
element.style.display.toLowerCase() != 'none' &&
element.style.visibility.toLowerCase() != 'hidden') {
addedMessage = true;
element.innerHTML = message;
}
}
}
if (!addedMessage) {
if (confirm(subMessage + alertMessage)) {
alertFunction();
}
}
};
Utility to get the text contents of a DOM element with a particular ID.
Currently only supports textarea and script nodes.
parameter: {string} id The Node id.
returns: {string} The text content.
o3djs.util.getElementContentById = function(id) {
// DOM manipulation is not currently supported in IE.
o3djs.BROWSER_ONLY;
var node = document.getElementById(id);
if (!node) {
throw 'getElementContentById could not find node with id ' + id;
}
switch (node.tagName) {
case 'TEXTAREA':
return node.value;
case 'SCRIPT':
return node.text;
default:
throw 'getElementContentById does not no how to get content from a ' +
node.tagName + ' element';
}
return node.value;
};
Utility to get an element from the DOM by ID. This must be used from V8
in preference to document.getElementById because we do not currently
support invoking methods on DOM objects in IE.
parameter: {string} id The Node id.
returns: {Node} The node or null if not found.
o3djs.util.getElementById = function(id) {
o3djs.BROWSER_ONLY;
return document.getElementById(id);
};
Identifies a JavaScript engine.
o3djs.util.Engine = {
The JavaScript engine provided by the browser.
BROWSER: 0,
The V8 JavaScript engine embedded in the plugin.
V8: 1
};
The engine selected as the main engine (the one the makeClients callback
will be invoked on).
@private
@type {o3djs.util.Engine}
o3djs.util.mainEngine_ = o3djs.util.Engine.BROWSER;
Select an engine to use as the main engine (the one the makeClients
callback will be invoked on). If an embedded engine is requested, one
element must be identified with the id 'o3d'. The callback will be invoked
in this element.
parameter: {o3djs.util.Engine} engine The engine.
o3djs.util.setMainEngine = function(engine) {
o3djs.util.mainEngine_ = engine;
};
A regex used to cleanup the string representation of a function before
it is evaled.
@private
@type {Regex}
o3djs.util.fixFunctionString_ = /^\s*function\s+[^\s]+\s*\(([^)]*)\)/
Evaluate a callback function in the V8 engine.
parameter: {!Object} clientElement The plugin containing the V8 engine.
parameter: {!function} callback A function to call.
parameter: {!Object} thisArg The value to be bound to "this".
parameter: {!Array.<*>} args The arguments to pass to the callback.
returns: {*} The result of calling the callback.
o3djs.util.callV8 = function(clientElement, callback, thisArg, args) {
// Sometimes a function will be converted to a string like this:
// function foo(a, b) { ... }
// In this case, convert to this form:
// function(a, b) { ... }
var functionString = callback.toString();
functionString = functionString.replace(o3djs.util.fixFunctionString_,
'function($1)');
// Make a V8 function that will invoke the callback.
var v8Code =
'function(thisArg, args) {\n' +
' var localArgs = [];\n' +
' var numArgs = args.length;\n' +
' for (var i = 0; i < numArgs; ++i) {\n' +
' localArgs.push(args[i]);\n' +
' }\n' +
' var func = ' + functionString + ';\n' +
' return func.apply(thisArg, localArgs);\n' +
'}\n';
// Evaluate the function in V8.
var v8Function = clientElement.eval(v8Code);
return v8Function(thisArg, args);
};
A regex to remove .. from a URI.
@private
@type {Regex}
o3djs.util.stripDotDot_ = /\/[^\/]+\/\.\./;
Turn a URI into an absolute URI.
parameter: {string} uri The URI.
returns: {string} The absolute URI.
o3djs.util.toAbsoluteUri = function(uri) {
if (uri.indexOf('://') == -1) {
var baseUri = document.location.toString();
var lastSlash = baseUri.lastIndexOf('/');
if (lastSlash != -1) {
baseUri = baseUri.substring(0, lastSlash);
}
uri = baseUri + '/' + uri;
}
do {
var lastUri = uri;
uri = uri.replace(o3djs.util.stripDotDot_, '');
} while (lastUri !== uri);
return uri;
};
The script URIs.
@type {!Array.<string>}
o3djs.util.scriptUris_ = [];
Add a script URI. Scripts that are referenced from script tags that are
within this URI are automatically loaded into the alternative JavaScript
main JavaScript engine. Do not include directories of scripts that are
included with o3djs.require. These are always available. This mechanism
is not able to load scripts in a different domain from the document.
parameter: {string} uri The URI.
o3djs.util.addScriptUri = function(uri) {
o3djs.util.scriptUris_.push(o3djs.util.toAbsoluteUri(uri));
};
Determine whether a URI is a script URI that should be loaded into the
alternative main JavaScript engine.
parameter: {string} uri The URI.
returns: {boolean} Whether it is a script URI.
o3djs.util.isScriptUri = function(uri) {
uri = o3djs.util.toAbsoluteUri(uri);
for (var i = 0; i < o3djs.util.scriptUris_.length; ++i) {
var scriptUri = o3djs.util.scriptUris_[i];
if (uri.substring(0, scriptUri.length) === scriptUri) {
return true;
}
}
return false;
};
Concatenate the text of all the script tags in the document and invokes
the callback when complete. This function is asynchronous if any of the
script tags reference JavaScript through a URI.
returns: {string} The script tag text.
o3djs.util.getScriptTagText_ = function() {
var scriptTagText = '';
var scriptElements = document.getElementsByTagName('script');
for (var i = 0; i < scriptElements.length; ++i) {
var scriptElement = scriptElements[i];
if (scriptElement.type === '' ||
scriptElement.type === 'text/javascript') {
if ('text' in scriptElement && scriptElement.text) {
scriptTagText += scriptElement.text;
}
if ('src' in scriptElement && scriptElement.src &&
o3djs.util.isScriptUri(scriptElement.src)) {
// It would be better to make this an asynchronous load but the script
// file is very likely to be in the browser cache because it should
// have just been loaded via the browser script tag.
scriptTagText += o3djs.io.loadTextFileSynchronous(scriptElement.src);
}
}
}
return scriptTagText;
};
Creates a client element. In other words it creates an <OBJECT> tag for
o3d. Note that the browser may not have initialized the plugin before
returning.
parameter: {string} opt_requestVersion version string in
"major.minor.revision.build" format. You can leave out any non-important
numbers for example "3" = request major version 3, "2.4" = request major
version 2, minor version 4. If no string is passed in the newest version
of the plugin will be created.
returns: {element} O3D element.
o3djs.util.createClient = function(opt_requestVersion) {
var objElem = document.createElement('object');
// TODO: Use opt_requiredVersion to set a version so the plugin
// can make sure it offers that version of the API.
// objElem.version = opt_requestVersion;
if (o3djs.base.IsMSIE()) {
objElem.classid = 'CLSID:9666A772-407E-4F90-BC37-982E8160EB2D';
} else {
objElem.type = 'application/vnd.o3d.auto';
}
return objElem;
};
Finds all divs with the id "o3d.*
o3djs.util.makeClients = function(callback,
opt_requiredVersion,
opt_failureCallback,
opt_id,
opt_tag,
opt_noGraphicsCallback) {
var tag = opt_tag || 'div';
var id = opt_id || '^o3d';
opt_failureCallback = opt_failureCallback || o3djs.util.offerPlugin;
opt_noGraphicsCallback = opt_noGraphicsCallback ||
o3djs.util.informNoGraphics;
opt_requiredVersion = opt_requiredVersion ||
o3djs.util.REQUIRED_VERSION;
if (!o3djs.util.requiredVersionAvailable(opt_requiredVersion)) {
opt_failureCallback(opt_id, opt_tag);
} else {
var clientElements = [];
var elements = document.getElementsByTagName(tag);
var mainClientElement = null;
for (var ee = 0; ee < elements.length; ++ee) {
var element = elements[ee];
if (element.id && element.id.match(id)) {
var objElem = o3djs.util.createClient();
objElem.style.width = '100%';
objElem.style.height = '100%';
element.appendChild(objElem);
clientElements.push(objElem);
// If the callback is to be invoked in an embedded JavaScript engine,
// one element must be identified with the id 'o3d'. This callback
// will be invoked in the element identified as such.
if (element.id === 'o3d') {
mainClientElement = objElem;
}
}
}
// Chrome 1.0 sometimes doesn't create the plugin instance. To work
// around this, force a re-layout by changing the plugin size until it
// is loaded. We toggle between 1 pixel and 100% until the plugin has
// loaded.
var chromeWorkaround = o3djs.base.IsChrome10();
{
// Wait for the browser to initialize the clients.
var clearId = window.setInterval(function() {
var initStatus = 0;
var o3d;
for (var cc = 0; cc < clientElements.length; ++cc) {
var element = clientElements[cc];
o3d = element.o3d;
if (!o3d) {
if (chromeWorkaround) {
if (element.style.width != '100%') {
element.style.width = '100%';
} else {
element.style.width = '1px';
}
}
return;
}
if (chromeWorkaround && element.style.width != '100%') {
// The plugin has loaded but it may not be the right size yet.
element.style.width = '100%';
return;
}
var status = clientElements[cc].client.rendererInitStatus;
// keep the highest status. This is the worst status.
if (status > initStatus) {
initStatus = status;
}
}
window.clearInterval(clearId);
// If the plugin could not initialize the graphics delete all of
// the plugin objects
if (initStatus > 0 && initStatus != o3d.Renderer.SUCCESS) {
for (var cc = 0; cc < clientElements.length; ++cc) {
var clientElement = clientElements[cc];
clientElement.parentNode.removeChild(clientElement);
}
opt_noGraphicsCallback(initStatus, o3d, opt_id, opt_tag);
} else {
o3djs.base.snapshotProvidedNamespaces();
// TODO: Is this needed with the new event code?
for (var cc = 0; cc < clientElements.length; ++cc) {
o3djs.base.initV8(clientElements[cc]);
o3djs.event.startKeyboardEventSynthesis(clientElements[cc]);
o3djs.error.setDefaultErrorHandler(clientElements[cc].client);
}
o3djs.base.init(clientElements[0]);
switch (o3djs.util.mainEngine_) {
case o3djs.util.Engine.BROWSER:
callback(clientElements);
break;
case o3djs.util.Engine.V8:
if (!mainClientElement) {
throw 'V8 engine was requested but there is no element with' +
' the id "o3d"';
}
// Retreive the code from the script tags and eval it in V8 to
// duplicate the browser environment.
var scriptTagText = o3djs.util.getScriptTagText_();
mainClientElement.eval(scriptTagText);
// Invoke the vallback in V8.
o3djs.util.callV8(mainClientElement,
callback,
o3djs.global,
[clientElements]);
break;
default:
throw 'Unknown engine ' + o3djs.util.mainEngine_;
}
}
}, 10);
}
}
};
}
(C) Æliens
20/2/2008
You may not copy or print any of this material without explicit permission of the author or the publisher.
In case of other copyright issues, contact the author.