topical media & game development

talk show tell print

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.