topical media & game development
graphic-o3d-samples-home-configurators-viewer.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.
*/
This file contains definitions for the common functions used by all the home
configurator pages.
o3djs.require('o3djs.util');
o3djs.require('o3djs.arcball');
o3djs.require('o3djs.dump');
o3djs.require('o3djs.rendergraph');
o3djs.require('o3djs.shape');
o3djs.require('o3djs.effect');
o3djs.require('o3djs.material');
o3djs.require('o3djs.pack');
o3djs.require('o3djs.picking');
o3djs.require('o3djs.scene');
var g_root;
var g_o3d;
var g_math;
var g_client;
var g_thisRot;
var g_lastRot;
var g_pack = null;
var g_mainPack;
var g_viewInfo;
var g_lightPosParam;
var g_currentTool = null;
var g_floorplanRoot = null;
var g_placedModesRoot = null;
var TOOL_ORBIT = 3;
var TOOL_MOVE = 1;
var TOOL_ZOOM_EXTENTS = 6;
var g_urlToInsert;
var g_isDraggingItem = false;
var g_o3dElement = null;
var g_lastMouseButtonState = 0;
Retrieve the absolute position of an element on the screen.
function getAbsolutePosition(element) {
var r = { x: element.offsetLeft, y: element.offsetTop };
if (element.offsetParent) {
var tmp = getAbsolutePosition(element.offsetParent);
r.x += tmp.x;
r.y += tmp.y;
}
return r;
}
Retrieve the coordinates of the given event relative to the center
of the widget.
parameter: event
A mouse-related DOM event.
parameter: reference
A DOM element whose position we want to transform the mouse coordinates to.
@return
An object containing keys 'x' and 'y'.
function getRelativeCoordinates(event, reference) {
var x, y;
event = event || window.event;
var el = event.target || event.srcElement;
if (!window.opera && typeof event.offsetX != 'undefined') {
// Use offset coordinates and find common offsetParent
var pos = { x: event.offsetX, y: event.offsetY };
// Send the coordinates upwards through the offsetParent chain.
var e = el;
while (e) {
e.mouseX = pos.x;
e.mouseY = pos.y;
pos.x += e.offsetLeft;
pos.y += e.offsetTop;
e = e.offsetParent;
}
// Look for the coordinates starting from the reference element.
var e = reference;
var offset = { x: 0, y: 0 }
while (e) {
if (typeof e.mouseX != 'undefined') {
x = e.mouseX - offset.x;
y = e.mouseY - offset.y;
break;
}
offset.x += e.offsetLeft;
offset.y += e.offsetTop;
e = e.offsetParent;
}
// Reset stored coordinates
e = el;
while (e) {
delete e.mouseX;
delete e.mouseY;
e = e.offsetParent;
}
}
else {
// Use absolute coordinates
var pos = getAbsolutePosition(reference);
x = event.pageX - pos.x;
y = event.pageY - pos.y;
}
return { x: x, y: y };
}
// The target camera has its z and y flipped because that's the way Scott
// Lininger thinks.
function TargetCamera() {
this.eye = {
rotZ: -Math.PI / 3,
rotH: Math.PI / 3,
distanceFromTarget: 700 };
this.target = { x: 0, y: 0, z: 0 };
}
TargetCamera.prototype.update = function() {
var target = [this.target.x, this.target.y, this.target.z];
this.eye.x = this.target.x + Math.cos(this.eye.rotZ) *
this.eye.distanceFromTarget * Math.sin(this.eye.rotH);
this.eye.y = this.target.y + Math.sin(this.eye.rotZ) *
this.eye.distanceFromTarget * Math.sin(this.eye.rotH);
this.eye.z = this.target.z + Math.cos(this.eye.rotH) *
this.eye.distanceFromTarget;
var eye = [this.eye.x, this.eye.y, this.eye.z];
var up = [0, 0, 1];
g_viewInfo.drawContext.view = g_math.matrix4.lookAt(eye, target, up);
g_lightPosParam.value = eye;
};
var g_camera = new TargetCamera();
function peg(value, lower, upper) {
if (value < lower) {
return lower;
} else if (value > upper) {
return upper;
} else {
return value;
}
}
Keyboard constants.
var BACKSPACE = 8;
var TAB = 9;
var ENTER = 13;
var SHIFT = 16;
var CTRL = 17;
var ALT = 18;
var ESCAPE = 27;
var PAGEUP = 33;
var PAGEDOWN = 34;
var END = 35;
var HOME = 36;
var LEFT = 37;
var UP = 38;
var RIGHT = 39;
var DOWN = 40;
var DELETE = 46;
var SPACE = 32;
Create some global key capturing. Keys that are pressed will be stored in
this global array.
g_keyIsDown = [];
document.onkeydown = function(e) {
var keycode;
if (window.event) {
keycode = window.event.keyCode;
} else if (e) {
keycode = e.which;
}
g_keyIsDown[keycode] = true;
if (g_currentTool != null) {
g_currentTool.handleKeyDown(keycode);
}
};
document.onkeyup = function(e) {
var keycode;
if (window.event) {
keycode = window.event.keyCode;
} else if (e) {
keycode = e.which;
}
g_keyIsDown[keycode] = false;
if (g_currentTool != null) {
g_currentTool.handleKeyUp(keycode);
}
};
document.onmouseup = function(e) {
if (g_currentTool != null) {
g_currentTool.handleMouseUp(e);
} else {
cancelInsertDrag();
}
};
// NOTE: mouseDown, mouseMove and mouseUp are mouse event handlers for events
// taking place inside the o3d area. They typically pass the events down
// to the currently selected tool (e.g. Orbit, Move, etc). Tool and item
// selection mouse events are registered seperately on their respective DOM
// elements.
// This function handles the mousedown events that happen inside the o3d
// area. If a tool is currently selected (e.g. Orbit, Move, etc.) the event
// is forwarded over to it. If the middle mouse button is pressed then we
// temporarily switch over to the orbit tool to emulate the SketchUp behavior.
function mouseDown(e) {
// If the middle mouse button is used, then switch into the orbit tool,
// Sketchup-style.
if (e.button == g_o3d.Event.BUTTON_MIDDLE) {
g_lastTool = g_currentTool;
g_lastSelectorLeft = $('toolselector').style.left;
selectTool(null, TOOL_ORBIT);
}
if (g_currentTool != null) {
g_currentTool.handleMouseDown(e);
}
}
// This function handles mouse move events inside the o3d area. It simply
// forwards them down to the currently selected tool.
function mouseMove(e) {
if (g_currentTool != null) {
g_currentTool.handleMouseMove(e);
}
}
// This function handles mouse up events that take place in the o3d area.
// If the middle mouse button is lifted then we switch out of the temporary
// orbit tool mode.
function mouseUp(e) {
// If the middle mouse button was used, then switch out of the orbit tool
// and reset to their last tool.
if (e.button == g_o3d.Event.BUTTON_MIDDLE) {
$('toolselector').style.left = g_lastSelectorLeft;
g_currentTool = g_lastTool
}
if (g_currentTool != null) {
g_currentTool.handleMouseUp(e);
}
}
function scrollMe(e) {
e = e ? e : window.event;
var raw = e.detail ? e.detail : -e.wheelDelta;
if (raw < 0) {
g_camera.eye.distanceFromTarget *= 11 / 12;
} else {
g_camera.eye.distanceFromTarget *= (1 + 1 / 12);
}
g_camera.update();
}
function name {
return document.getElementById(name);
}
// An array of tool objects that will get populated when our base model loads.
var g_tools = [];
function selectTool(e, opt_toolNumber) {
var ICON_WIDTH = 32;
var toolNumber = opt_toolNumber;
if (toolNumber == undefined) {
// Where you click determines your tool. But since our underlying toolbar
// graphic isn't perfectly uniform, perform some acrobatics to get the best
// toolNumber match.
var pt = getRelativeCoordinates(e, $('toolpanel'));
if (pt.x < 120) {
toolNumber = Math.floor((pt.x - 8) / ICON_WIDTH)
} else {
toolNumber = Math.floor((pt.x - 26) / ICON_WIDTH)
}
toolNumber = peg(toolNumber, 0, 9);
}
// Now place the selector graphic over the tool we clicked.
if (toolNumber < 3) {
$('toolselector').style.left = toolNumber * ICON_WIDTH + 8;
} else {
$('toolselector').style.left = toolNumber * ICON_WIDTH + 26;
}
// Finally, activate the appropriate tool.
// TODO: Replace this hacky zoom extents tool detection with
// tools that have an onActivate callback.
if (toolNumber == TOOL_ZOOM_EXTENTS) {
// Reset our camera. (Zooming to extents would be better, obviously ;)
g_camera.eye.distanceFromTarget = 900;
g_camera.target = { x: -100, y: 0, z: 0 };
g_camera.update();
// Then reset to the orbit tool, after pausing for a bit so the user
// sees the zoom extents button depress.
setTimeout(function() { selectTool(null, TOOL_ORBIT)}, 200);
} else {
g_currentTool = g_tools[toolNumber];
}
}
function loadFile(context, path) {
function callback(pack, start_move_tool_root, exception) {
if (exception) {
alert('Could not load: ' + path + '\n' + exception);
} else {
// Generate draw elements and setup material draw lists.
o3djs.pack.preparePack(g_pack, g_viewInfo);
// Manually connect all the materials' lightWorldPos params to the context
var materials = g_pack.getObjectsByClassName('o3d.Material');
for (var m = 0; m < materials.length; ++m) {
var material = materials[m];
var param = material.getParam('lightWorldPos');
if (param) {
param.bind(g_lightPosParam);
}
}
/*
o3djs.dump.dump('---dumping context---\n');
o3djs.dump.dumpParamObject(context);
o3djs.dump.dump('---dumping root---\n');
o3djs.dump.dumpTransformTree(g_client.root);
o3djs.dump.dump('---dumping render root---\n');
o3djs.dump.dumpRenderNodeTree(g_client.renderGraphRoot);
o3djs.dump.dump('---dump g_pack shapes---\n');
var shapes = g_pack.getObjectsByClassName('o3d.Shape');
for (var t = 0; t < shapes.length; t++) {
o3djs.dump.dumpShape(shapes[t]);
}
o3djs.dump.dump('---dump g_pack materials---\n');
var materials = g_pack.getObjectsByClassName('o3d.Material');
for (var t = 0; t < materials.length; t++) {
o3djs.dump.dump (
' ' + t + ' : ' + materials[t].className +
' : "' + materials[t].name + '"\n');
o3djs.dump.dumpParams(materials[t], ' ');
}
o3djs.dump.dump('---dump g_pack textures---\n');
var textures = g_pack.getObjectsByClassName('o3d.Texture');
for (var t = 0; t < textures.length; t++) {
o3djs.dump.dumpTexture(textures[t]);
}
o3djs.dump.dump('---dump g_pack effects---\n');
var effects = g_pack.getObjectsByClassName('o3d.Effect');
for (var t = 0; t < effects.length; t++) {
o3djs.dump.dump (' ' + t + ' : ' + effects[t].className +
' : "' + effects[t].name + '"\n');
o3djs.dump.dumpParams(effects[t], ' ');
}
*/
}
if (start_move_tool_root != g_floorplanRoot) {
selectTool(null, TOOL_MOVE);
g_tools[TOOL_MOVE].initializeWithShape(start_move_tool_root);
}
g_camera.update();
}
g_pack = g_client.createPack();
g_pack.name = 'load pack';
new_object_root = null;
if (g_floorplanRoot == null) {
// Assign as the floorplan
g_floorplanRoot = g_pack.createObject('o3d.Transform');
g_floorplanRoot.name = 'floorplan';
g_floorplanRoot.parent = g_client.root;
g_placedModelsRoot = g_pack.createObject('o3d.Transform');
g_placedModelsRoot.name = 'placedModels';
g_placedModelsRoot.parent = g_floorplanRoot;
// Put the object we're loading on the floorplan.
new_object_root = g_floorplanRoot;
// Create our set of tools that can be activated.
// Note: Currently only the Delete, Move, Rotate, Orbit, Pan and Zoom
// tools are implemented. The last four icons in the toolbar are unused.
g_tools = [
new DeleteTool(g_viewInfo.drawContext, g_placedModelsRoot),
new MoveTool(g_viewInfo.drawContext, g_placedModelsRoot),
new RotateTool(g_viewInfo.drawContext, g_placedModelsRoot),
new OrbitTool(g_camera),
new PanTool(g_camera),
new ZoomTool(g_camera),
null,
null,
null,
null
]
selectTool(null, TOOL_ORBIT);
} else {
// Create a new transform for the loaded file
new_object_root = g_pack.createObject('o3d.Transform');
new_object_root.name = 'loaded object';
new_object_root.parent = g_placedModelsRoot;
}
if (path != null) {
o3djs.scene.loadScene(g_client, g_pack, new_object_root, path, callback);
}
return new_object_root;
}
function setClientSize() {
// Create a perspective projection matrix
g_viewInfo.drawContext.projection = g_math.matrix4.perspective(
3.14 * 45 / 180, g_client.width / g_client.height, 0.1, 5000);
}
function resize() {
setClientSize();
}
function init() {
o3djs.util.makeClients(initStep2);
}
function initStep2(clientElements) {
g_o3dElement = clientElements[0];
var path = window.location.href;
var index = path.lastIndexOf('/');
path = path.substring(0, index + 1) + g_buildingAsset;
var url = document.getElementById('url').value = path;
g_o3d = g_o3dElement.o3d;
g_math = o3djs.math;
g_client = g_o3dElement.client;
g_mainPack = g_client.createPack();
g_mainPack.name = 'simple viewer pack';
// Create the render graph for a view.
g_viewInfo = o3djs.rendergraph.createBasicView(
g_mainPack,
g_client.root,
g_client.renderGraphRoot,
[1, 1, 1, 1]);
g_lastRot = g_math.identity(3);
g_thisRot = g_math.identity(3);
var root = g_client.root;
var target = [0, 0, 0];
var eye = [0, 0, 5];
var up = [0, 1, 0];
g_viewInfo.drawContext.view = g_math.matrix4.lookAt(eye, target, up);
setClientSize();
var paramObject = g_mainPack.createObject('ParamObject');
// Set the light at the same position as the camera to create a headlight
// that illuminates the object straight on.
g_lightPosParam = paramObject.createParam('lightWorldPos', 'ParamFloat3');
g_lightPosParam.value = eye;
doload();
o3djs.event.addEventListener(g_o3dElement, 'mousedown', mouseDown);
o3djs.event.addEventListener(g_o3dElement, 'mousemove', mouseMove);
o3djs.event.addEventListener(g_o3dElement, 'mouseup', mouseUp);
g_o3dElement.addEventListener('mouseover', dragOver, false);
// for Firefox
g_o3dElement.addEventListener('DOMMouseScroll', scrollMe, false);
// for Safari
g_o3dElement.onmousewheel = scrollMe;
document.getElementById('toolpanel').onmousedown = selectTool;
// Create our catalog list from the global list of items (g_items).
html = [];
for (var i = 0; i < g_items.length; i++) {
html.push('<div class="catalogItem" onmousedown="startInsertDrag(\'',
g_items[i].url, '\')" style="background-position:-', (i * 62) ,
'px 0px" title="', g_items[i].title, '"></div>');
}
$('itemlist').innerHTML = html.join('');
// Register a mouse-move event listener to the entire window so that we can
// catch the click-and-drag events that originate from the list of items
// and end up in the o3d element.
document.addEventListener('mousemove', mouseMove, false);
}
function dragOver(e) {
if (g_urlToInsert != null) {
doload(g_urlToInsert);
}
g_urlToInsert = null;
}
function doload(opt_url) {
var url;
if (opt_url != null) {
url = opt_url
} else if ($('url')) {
url = $('url').value;
}
g_root = loadFile(g_viewInfo.drawContext, url);
}
function startInsertDrag(url) {
// If no absolute web path was passed, assume it's a local file
// coming from the assets directory.
if (url.indexOf('http') != 0) {
var path = window.location.href;
var index = path.lastIndexOf('/');
g_urlToInsert = path.substring(0, index + 1) + g_assetPath + url;
} else {
g_urlToInsert = url;
}
}
function cancelInsertDrag() {
g_urlToInsert = null;
}
(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.