var Thingiview = function(containerId, gridsize, gridunit) {
  this.scope = this;
  this.containerId  = containerId;
  this.gridsize = gridsize;
  this.gridunit = gridunit;
  this.init();
}

Thingiview.prototype.init = function() {
  this.container     = document.getElementById(this.containerId);
  
  // this.stats    = null;
  this.camera   = null;
  this.scene    = null;
  this.renderer = null;
  this.object   = null;
  this.plane    = null;
  
  this.ambientLight     = null;
  this.directionalLight = null;
  this.pointLight       = null;
  
  this.targetXRotation             = 0;
  this.targetXRotationOnMouseDown  = 0;
  this.mouseX                      = 0;
  this.mouseXOnMouseDown           = 0;

  this.targetYRotation             = 0;
  this.targetYRotationOnMouseDown  = 0;
  this.mouseY                      = 0;
  this.mouseYOnMouseDown           = 0;

  this.mouseDown                  = false;
  this.mouseOver                  = false;
  
  this.windowHalfX = window.innerWidth / 2;
  this.windowHalfY = window.innerHeight / 2

  this.view         = null;
  this.infoMessage  = null;
  this.progressBar  = null;
  this.alertBox     = null;
  
  this.timer        = null;

  this.rotateTimer    = null;
  this.rotateListener = null;
  this.wasRotating    = null;

  this.cameraView = 'diagonal';
  this.cameraZoom = 0;
  this.rotate = false;
  this.backgroundColor = '#606060';
  this.objectMaterial = 'solid';
  this.objectColor = 0xffffff;
  this.showPlane = true;
  this.isWebGl = false;

  if (document.defaultView && document.defaultView.getComputedStyle) {
    this.width  = parseFloat(document.defaultView.getComputedStyle(this.container,null).getPropertyValue('width'));
    this.height = parseFloat(document.defaultView.getComputedStyle(this.container,null).getPropertyValue('height'));  
  } else {
    this.width  = parseFloat(this.container.currentStyle.width);
    this.height = parseFloat(this.container.currentStyle.height);
  }

  this.geometry;
}

Thingiview.prototype.initScene = function() {
    this.container.style.position = 'relative';
    this.container.innerHTML      = '';

  	this.camera = new THREE.PerspectiveCamera(45, this.width/ this.height, 1, 100000);
  	this.camera.up = new THREE.Vector3(0, 0, 1);
  	this.camera.target = new THREE.Vector3(0, 0, 0);
  	this.camera.updateMatrix();

  	this.scene  = new THREE.Scene();

    this.ambientLight = new THREE.AmbientLight(0x202020);
    this.scene.add(this.ambientLight);
    
    this.directionalLight = new THREE.DirectionalLight(0xffffff, 0.75);
    this.directionalLight.position.x = 1;
    this.directionalLight.position.y = 1;
    this.directionalLight.position.z = 2;
    this.directionalLight.position.normalize();
    this.scene.add(this.directionalLight);
    
    this.pointLight = new THREE.PointLight(0xffffff, 0.3);
    this.pointLight.position.x = 0;
    this.pointLight.position.y = -25;
    this.pointLight.position.z = 10;
    this.scene.add(this.pointLight);

    this.progressBar = document.createElement('div');
    this.progressBar.style.position = 'absolute';
    this.progressBar.style.top = '0px';
    this.progressBar.style.left = '0px';
    this.progressBar.style.backgroundColor = 'red';
    this.progressBar.style.padding = '5px';
    this.progressBar.style.display = 'none';
    this.progressBar.style.overflow = 'visible';
    this.progressBar.style.whiteSpace = 'nowrap';
    this.progressBar.style.zIndex = 100;
    this.container.appendChild(this.progressBar);
    
    alertBox = document.createElement('div');
    alertBox.id = 'alertBox';
    alertBox.style.position = 'absolute';
    alertBox.style.top = '25%';
    alertBox.style.left = '25%';
    alertBox.style.width = '50%';
    alertBox.style.height = '50%';
    alertBox.style.backgroundColor = '#dddddd';
    alertBox.style.padding = '10px';
    // alertBox.style.overflowY = 'scroll';
    alertBox.style.display = 'none';
    alertBox.style.zIndex = 100;
    this.container.appendChild(alertBox);
    
    // load a blank object
    // this.loadSTLString('');

    if (this.showPlane) {
      this.loadPlaneGeometry();
    }
    
    this.setCameraView(this.cameraView);
    this.setObjectMaterial(this.objectMaterial);

    testCanvas = document.createElement('canvas');
    try {
      if (testCanvas.getContext('experimental-webgl')) {
        // this.showPlane = false;
        isWebGl = true;
        this.renderer = new THREE.WebGLRenderer();
        // this.renderer = new THREE.CanvasRenderer();
      } else {
        this.renderer = new THREE.CanvasRenderer();
      }
    } catch(e) {
      this.renderer = new THREE.CanvasRenderer();
      // log("failed webgl detection");
    }

    // this.renderer.setSize(this.container.innerWidth, this.container.innerHeight);

  	this.renderer.setSize(this.width, this.height);
    this.renderer.domElement.style.backgroundColor = this.backgroundColor;
  	this.container.appendChild(this.renderer.domElement);

    // stats = new Stats();
    // stats.domElement.style.position  = 'absolute';
    // stats.domElement.style.top       = '0px';
    // this.container.appendChild(stats.domElement);

    // TODO: figure out how to get the render window to resize when window resizes
    // window.addEventListener('resize', onContainerResize(), false);
    // this.container.addEventListener('resize', onContainerResize(), false);

    // this.renderer.domElement.addEventListener('mousemove',      onRendererMouseMove,     false);    
  	window.addEventListener('mousemove', (function(self) {
                                  return function(event) {
                                    self.onRendererMouseMove(event);
                                  }
                                 })(this),     false);    
    this.renderer.domElement.addEventListener('mouseover', (function(self) {
                                  return function(event) {
                                    self.onRendererMouseOver(event);
                                  }
                                 })(this),     false);
    this.renderer.domElement.addEventListener('mouseout', (function(self) {
                                  return function(event) {
                                    self.onRendererMouseOut(event);
                                  }
                                 })(this),      false);
  	this.renderer.domElement.addEventListener('mousedown', (function(self) {
                                  return function(event) {
                                    self.onRendererMouseDown(event);
                                  }
                                 })(this),     false);
    // this.renderer.domElement.addEventListener('mouseup',        this.onRendererMouseUp,       false);
    window.addEventListener('mouseup', (function(self) {
                                  return function(event) {
                                    self.onRendererMouseUp(event);
                                  }
                                 })(this),       false);

  	this.renderer.domElement.addEventListener('touchstart', (function(self) {
                                  return function(event) {
                                    self.onRendererTouchStart(event);
                                  }
                                 })(this),    false);
  	this.renderer.domElement.addEventListener('touchend', (function(self) {
                                  return function(event) {
                                    self.onRendererTouchEnd(event);
                                  }
                                 })(this),      false);
  	this.renderer.domElement.addEventListener('touchmove', (function(self) {
                                  return function(event) {
                                    self.onRendererTouchMove(event);
                                  }
                                 })(this),     false);

    this.renderer.domElement.addEventListener('DOMMouseScroll', (function(self) {
                                  return function(event) {
                                    self.onRendererScroll(event);
                                  }
                                 })(this),        false);
  	this.renderer.domElement.addEventListener('mousewheel', (function(self) {
                                  return function(event) {
                                    self.onRendererScroll(event);
                                  }
                                 })(this),        false);
  	this.renderer.domElement.addEventListener('gesturechange', (function(self) {
                                  return function(event) {
                                    self.onRendererGestureChange(event);
                                  }
                                 })(this), false);
  }

  // FIXME
  // onContainerResize = function(event) {
  //   width  = parseFloat(document.defaultView.getComputedStyle(this.container,null).getPropertyValue('width'));
  //   height = parseFloat(document.defaultView.getComputedStyle(this.container,null).getPropertyValue('height'));
  // 
  //   // log("resized width: " + width + ", height: " + height);
  // 
  //   if (this.renderer) {
  //     this.renderer.setSize(width, height);
  //     camera.projectionMatrix = THREE.Matrix4.makePerspective(70, width / height, 1, 10000);
  //     this.sceneLoop();
  //   }    
  // };
  
Thingiview.prototype.onRendererScroll = function(event) {
    event.preventDefault();

    var rolled = 0;

    if (event.wheelDelta === undefined) {
      // Firefox
      // The measurement units of the detail and wheelDelta properties are different.
      rolled = -40 * event.detail;
    } else {
      rolled = event.wheelDelta;
    }

    if (rolled > 0) {
      // up
      this.scope.setCameraZoom(+10);
    } else {
      // down
      this.scope.setCameraZoom(-10);
    }
  }

Thingiview.prototype.onRendererGestureChange = function(event) {
    event.preventDefault();

    if (event.scale > 1) {
      this.scope.setCameraZoom(+5);
    } else {
      this.scope.setCameraZoom(-5);
    }
  }

Thingiview.prototype.onRendererMouseOver = function(event) {
    this.mouseOver = true;
    // targetRotation = object.rotation.z;
    if (this.timer == null) {
      // log('starting loop');
      this.timer = setInterval((function(self) {
                                  return function() {
                                    self.sceneLoop();
                                  }
                                 })(this), 1000/60);
    }
  }

Thingiview.prototype.onRendererMouseDown = function(event) {
    // log("down");

    event.preventDefault();
  	this.mouseDown = true;
    
    if(this.scope.getRotation()){
      this.wasRotating = true;
      this.setRotation(false);
    } else {
      this.wasRotating = false;
    }
    
  	mouseXOnMouseDown = event.clientX - this.windowHalfX;
  	mouseYOnMouseDown = event.clientY - this.windowHalfY;

  	this.targetXRotationOnMouseDown = this.targetXRotation;
  	this.targetYRotationOnMouseDown = this.targetYRotation;
  }

Thingiview.prototype.onRendererMouseMove = function(event) {
    // log("move");

    if (this.mouseDown) {
  	  mouseX = event.clientX - this.windowHalfX;
      // this.targetXRotation = this.targetXRotationOnMouseDown + (mouseX - mouseXOnMouseDown) * 0.02;
  	  xrot = this.targetXRotationOnMouseDown + (mouseX - mouseXOnMouseDown) * 0.02;

  	  mouseY = event.clientY - this.windowHalfY;
      // this.targetYRotation = this.targetYRotationOnMouseDown + (mouseY - mouseYOnMouseDown) * 0.02;
  	  yrot = this.targetYRotationOnMouseDown + (mouseY - mouseYOnMouseDown) * 0.02;
  	  
  	  this.targetXRotation = xrot;
  	  this.targetYRotation = yrot;
	  }
  }

Thingiview.prototype.onRendererMouseUp = function(event) {
    // log("up");
    if (this.mouseDown) {
      this.mouseDown = false;
      if (!this.mouseOver) {
        clearInterval(this.timer);
        this.timer = null;
      }
      if (this.wasRotating) {
        this.setRotation(true);
      }
    }
  }

Thingiview.prototype.onRendererMouseOut = function(event) {
    if (!this.mouseDown) {
      clearInterval(this.timer);
      this.timer = null;
    }
    this.mouseOver = false;
  }

Thingiview.prototype.onRendererTouchStart = function(event) {
    this.targetXRotation = this.object.rotation.z;
    this.targetYRotation = this.object.rotation.x;

    this.timer = setInterval((function(self) {
                                return function() {
                                  self.sceneLoop();
                                }
                               })(this), 1000/60);

  	if (event.touches.length == 1) {
  		event.preventDefault();

  		mouseXOnMouseDown = event.touches[0].pageX - this.windowHalfX;
  		this.targetXRotationOnMouseDown = this.targetXRotation;

  		mouseYOnMouseDown = event.touches[0].pageY - this.windowHalfY;
  		this.targetYRotationOnMouseDown = this.targetYRotation;
  	}
  }

Thingiview.prototype.onRendererTouchEnd = function(event) {
    clearInterval(this.timer);
    this.timer = null;
    // this.targetXRotation = this.object.rotation.z;
    // this.targetYRotation = this.object.rotation.x;
  }

Thingiview.prototype.onRendererTouchMove = function(event) {
  	if (event.touches.length == 1) {
  		event.preventDefault();

  		mouseX = event.touches[0].pageX - this.windowHalfX;
  		this.targetXRotation = this.targetXRotationOnMouseDown + (mouseX - mouseXOnMouseDown) * 0.05;

  		mouseY = event.touches[0].pageY - this.windowHalfY;
  		this.targetYRotation = this.targetYRotationOnMouseDown + (mouseY - mouseYOnMouseDown) * 0.05;
  	}
  }

Thingiview.prototype.sceneLoop = function() {
    if (this.object) {
      // if (view == 'bottom') {
      //   if (this.showPlane) {
      //     this.plane.rotation.z = this.object.rotation.z -= (targetRotation + this.object.rotation.z) * 0.05;
      //   } else {
      //     this.object.rotation.z -= (targetRotation + this.object.rotation.z) * 0.05;
      //   }
      // } else {
      //   if (this.showPlane) {
      //     this.plane.rotation.z = this.object.rotation.z += (targetRotation - this.object.rotation.z) * 0.05;
      //   } else {
      //     this.object.rotation.z += (targetRotation - this.object.rotation.z) * 0.05;
      //   }
      // }

      if (this.showPlane) {
        this.plane.rotation.z = this.object.rotation.z = (this.targetXRotation - this.object.rotation.z) * 0.2;
        this.plane.rotation.x = this.object.rotation.x = (this.targetYRotation - this.object.rotation.x) * 0.2;
      } else {
        this.object.rotation.z = (this.targetXRotation - this.object.rotation.z) * 0.2;
        this.object.rotation.x = (this.targetYRotation - this.object.rotation.x) * 0.2;
      }

      // log(this.object.rotation.x);

      this.camera.lookAt(this.camera.target);
      this.camera.updateMatrix();
      this.object.updateMatrix();

      if (this.showPlane) {
        this.plane.updateMatrix();
      }

    	this.renderer.render(this.scene, this.camera);
      // stats.update();
    }
  }

Thingiview.prototype.rotateLoop = function() {
    // targetRotation += 0.01;
    this.targetXRotation += 0.05;
    this.sceneLoop();
  }

Thingiview.prototype.getShowPlane = function(){
    return this.showPlane;
  }

Thingiview.prototype.setShowPlane = function(show) {
    this.showPlane = show;

    if (show) {
      log("Showing plane");
      if (this.scene && !this.plane) {
        this.loadPlaneGeometry();
      }
      this.scene.add(this.plane);
      this.plane.updateMatrix();
    } else {
      log("Hiding plane");
      if (this.scene && this.plane) {
        this.scene.remove(this.plane);
      }
    }
    
    this.sceneLoop();
  }

Thingiview.prototype.getRotation = function() {
    return this.rotateTimer !== null;
  }

Thingiview.prototype.setRotation = function(rotate) {
    log("Rotation set to " + rotate)
    
    clearInterval(this.rotateTimer);

    if (rotate) {
      this.rotateTimer = setInterval((function(self) {
                                        return function() {
                                          self.rotateLoop();
                                        }
                                       })(this), 1000/60);
    } else {
      this.rotateTimer = null;
    }

    this.scope.onSetRotation();
  }

Thingiview.prototype.onSetRotation = function(callback) {
    if(callback === undefined){
      if(this.rotateListener !== null){
        try{
          this.rotateListener(this.scope.getRotation());
        } catch(ignored) {}
      }
    } else {
      this.rotateListener = callback;
    }
  }

Thingiview.prototype.setCameraView = function(dir) {
    this.cameraView = dir;

    this.targetXRotation       = 0;
    this.targetYRotation       = 0;

    if (this.object) {
      this.object.rotation.x = 0;
      this.object.rotation.y = 0;
      this.object.rotation.z = 0;
    }

    if (this.showPlane && this.object) {
      this.plane.rotation.x = this.object.rotation.x;
      this.plane.rotation.y = this.object.rotation.y;
      this.plane.rotation.z = this.object.rotation.z;
    }
    
    if (dir == 'top') {
      // camera.position.y = 0;
      // this.camera.position.z = 100;
      // this.camera.target.z = 0;
      if (this.showPlane) {
        this.plane.flipSided = false;
      }
    } else if (dir == 'side') {
      // this.camera.position.y = -70;
      // this.camera.position.z = 70;
      // this.camera.target.z = 0;
      this.targetYRotation = -4.5;
      if (this.showPlane) {
        this.plane.flipSided = false;
      }
    } else if (dir == 'bottom') {
      // this.camera.position.y = 0;
      // this.camera.position.z = -100;
      // this.camera.target.z = 0;
      if (this.showPlane) {
        this.plane.flipSided = true;
      }
    } else {
      // this.camera.position.y = -70;
      // this.camera.position.z = 70;
      // this.camera.target.z = 0;
      if (this.showPlane) {
        this.plane.flipSided = false;
      }
    }

    mouseX            = this.targetXRotation;
    mouseXOnMouseDown = this.targetXRotation;
    
    mouseY            = this.targetYRotation;
    mouseYOnMouseDown = this.targetYRotation;
    
    this.scope.centerCamera();
  }

Thingiview.prototype.setCameraZoom = function(factor) {
    this.cameraZoom = factor;
    
    if (this.cameraView == 'bottom') {
      if (this.camera.position.z + factor > 0) {
        factor = 0;
      }
    } else {
      if (this.camera.position.z - factor < 0) {
        factor = 0;
      }
    }
    
    if (this.cameraView == 'top') {
      this.camera.position.z -= factor;
    } else if (this.cameraView == 'bottom') {
      this.camera.position.z += factor;
    } else if (this.cameraView == 'side') {
      this.camera.position.y += factor;
      this.camera.position.z -= factor;
    } else {
      this.camera.position.y += factor;
      this.camera.position.z -= factor;
    }

    this.sceneLoop();
  }

Thingiview.prototype.getObjectMaterial = function() {
    return this.objectMaterial;
  }

Thingiview.prototype.setObjectMaterial = function(type) {
    this.objectMaterial = type;

    this.loadObjectGeometry();
  }

Thingiview.prototype.setBackgroundColor = function(color) {
    backgroundColor = color
    
    if (this.renderer) {
      this.renderer.domElement.style.backgroundColor = color;
    }
  }

Thingiview.prototype.setObjectColor = function(color) {
    this.objectColor = parseInt(color.replace(/\#/g, ''), 16);
    
    this.loadObjectGeometry();
  }

Thingiview.prototype.loadSTL = function(url) {
    this.scope.newWorker('loadSTL', url);
  }

Thingiview.prototype.loadOBJ = function(url) {
    this.scope.newWorker('loadOBJ', url);
  }
  
Thingiview.prototype.loadSTLString = function(STLString) {
    this.scope.newWorker('loadSTLString', STLString);
  }
  
Thingiview.prototype.loadSTLBinary = function(STLBinary) {
    this.scope.newWorker('loadSTLBinary', STLBinary);
  }
  
Thingiview.prototype.loadOBJString = function(OBJString) {
    this.scope.newWorker('loadOBJString', OBJString);
  }

Thingiview.prototype.loadJSON = function(url) {
    this.scope.newWorker('loadJSON', url);
  }

Thingiview.prototype.loadPLY = function(url) {
    this.scope.newWorker('loadPLY', url);
  }
  
Thingiview.prototype.loadPLYString = function(PLYString) {
    this.scope.newWorker('loadPLYString', PLYString);
  }

Thingiview.prototype.loadPLYBinary = function(PLYBinary) {
    this.scope.newWorker('loadPLYBinary', PLYBinary);
  }

Thingiview.prototype.centerCamera = function() {
    if (this.geometry) {
      // Using method from http://msdn.microsoft.com/en-us/library/bb197900(v=xnagamestudio.10).aspx
      // log("bounding sphere radius = " + this.geometry.boundingSphere.radius);

      // look at the center of the object
      this.camera.target.x = this.geometry.center_x;
      this.camera.target.y = this.geometry.center_y;
      this.camera.target.z = this.geometry.center_z;

      // set camera position to center of sphere
      this.camera.position.x = this.geometry.center_x;
      this.camera.position.y = this.geometry.center_y;
      this.camera.position.z = this.geometry.center_z;

      // find distance to center
      distance = this.geometry.boundingSphere.radius / Math.sin((this.camera.fov/2) * (Math.PI / 180));

      // zoom backwards about half that distance, I don't think I'm doing the math or backwards vector calculation correctly?
      // this.scope.setCameraZoom(-distance/1.8);
      // this.scope.setCameraZoom(-distance/1.5);
      this.scope.setCameraZoom(-distance/1.2);

           this.directionalLight.position.x = 0; 
      this.directionalLight.position.y = 0;
      this.directionalLight.position.z = this.geometry.max_z * 2;

      this.pointLight.position.x = this.camera.position.x + 100;
      this.pointLight.position.y = this.camera.position.y;
      this.pointLight.position.z = this.camera.position.z + 100;

      this.sceneLoop();

    } else {
      // set to any valid position so it doesn't fail before geometry is available
      this.camera.position.y = -70;
      this.camera.position.z = 70;
      this.camera.target.z = 0;
    }
  }

Thingiview.prototype.loadArray = function(array) {
    log("loading array...");
    this.geometry = new STLGeometry(array);
    this.loadObjectGeometry();
    this.setRotation(false);
    this.setRotation(true);
    this.centerCamera();
    log("finished loading " + this.geometry.faces.length + " faces.");
  }

Thingiview.prototype.newWorker = function(cmd, param) {
    var wasRotating = this.getRotation();
    this.setRotation(false);
  	
    var worker = new WorkerFacade(thingiurlbase + '/thingiloader.js');
    worker.scope = this;
    
    worker.onmessage = function(event) {
      if (event.data.status == "complete") {
        this.scope.progressBar.innerHTML = 'Initializing geometry...';
        // this.scene.removeObject(this.object);
        this.scope.geometry = new STLGeometry(event.data.content);
        this.scope.loadObjectGeometry();
        this.scope.progressBar.innerHTML = '';
        this.scope.progressBar.style.display = 'none';

        this.scope.setRotation(wasRotating);
        log("finished loading " + this.scope.geometry.faces.length + " faces.");
        this.scope.centerCamera();
      } else if (event.data.status == "complete_points") {
        this.scope.progressBar.innerHTML = 'Initializing points...';

        this.scope.geometry = new THREE.Geometry();

        var material = new THREE.ParticleBasicMaterial( { color: 0xff0000, opacity: 1 } );

        // material = new THREE.ParticleBasicMaterial( { size: 35, sizeAttenuation: false} );
        // material.color.setHSV( 1.0, 0.2, 0.8 );
        
        for (i in event.data.content[0]) {
        // for (var i=0; i<10; i++) {
          vector = new THREE.Vector3( event.data.content[0][i][0], event.data.content[0][i][1], event.data.content[0][i][2] );
          this.scope.geometry.vertices.push(vector);
        }

        particles = new THREE.ParticleSystem( this.geometry, material );
        particles.sortParticles = true;
        particles.updateMatrix();
        this.scope.scene.add( particles );
                                
        this.scope.camera.lookAt(this.camera.target);
        this.scope.camera.updateMatrix();
        this.scope.renderer.render(this.scene, this.camera);
        
        this.scope.progressBar.innerHTML = '';
        this.scope.progressBar.style.display = 'none';

        this.scope.setRotation(wasRotating);
        log("finished loading " + event.data.content[0].length + " points.");
        // this.scope.centerCamera();
      } else if (event.data.status == "progress") {
        this.scope.progressBar.style.display = 'block';
        this.scope.progressBar.style.width = event.data.content;
        // log(event.data.content);
      } else if (event.data.status == "message") {
        this.scope.progressBar.style.display = 'block';
        this.scope.progressBar.innerHTML = event.data.content;
        log(event.data.content);
      } else if (event.data.status == "alert") {
        this.scope.displayAlert(event.data.content);
      } else {
        alert('Error: ' + event.data);
        log('Unknown Worker Message: ' + event.data);
      }
    }

    worker.onerror = function(error) {
      log(error);
      error.preventDefault();
    }

    worker.postMessage({'cmd':cmd, 'param':param});
  }

Thingiview.prototype.displayAlert = function(msg) {
    msg = msg + "<br/><br/><center><input type=\"button\" value=\"Ok\" onclick=\"document.getElementById('alertBox').style.display='none'\"></center>"
    
    alertBox.innerHTML = msg;
    alertBox.style.display = 'block';
    
    // log(msg);
  }

Thingiview.prototype.loadPlaneGeometry = function() {

    var material = new THREE.LineBasicMaterial({color:0xafafaf});
    this.plane = new THREE.Object3D();
    for (var i=-this.gridsize/2; i<= this.gridsize/2; i += this.gridunit) {
      var geometry = new THREE.Geometry();
      geometry.vertices.push(new THREE.Vector3 (i, -this.gridsize/2, 0));
      geometry.vertices.push(new THREE.Vector3 (i, this.gridsize/2, 0));
      var line = new THREE.Line(geometry, material);
      this.plane.add(line);
      geometry = new THREE.Geometry();
      geometry.vertices.push(new THREE.Vector3 (-this.gridsize/2, i, 0));
      geometry.vertices.push(new THREE.Vector3 (this.gridsize/2, i, 0));
      line = new THREE.Line(geometry, material);
      this.plane.add(line);
    }
   
  }

Thingiview.prototype.loadObjectGeometry = function() {
    if (this.scene && this.geometry) {
      if (this.objectMaterial == 'wireframe') {
        // material = new THREE.MeshColorStrokeMaterial(this.objectColor, 1, 1);
        material = new THREE.MeshBasicMaterial({color:this.objectColor,wireframe:true});
      } else {
        if (this.isWebGl) {
          // material = new THREE.MeshPhongMaterial(this.objectColor, this.objectColor, 0xffffff, 50, 1.0);
          // material = new THREE.MeshColorFillMaterial(this.objectColor);
          // material = new THREE.MeshLambertMaterial({color:this.objectColor});
          material = new THREE.MeshLambertMaterial({color:this.objectColor, shading: THREE.FlatShading});
        } else {
          // material = new THREE.MeshColorFillMaterial(this.objectColor);
          material = new THREE.MeshLambertMaterial({color:this.objectColor, shading: THREE.FlatShading});
        }
      }

      // scene.removeObject(this.object);      

      if (this.object) {
        // shouldn't be needed, but this fixes a bug with webgl not removing previous object when loading a new one dynamically
        this.object.materials = [new THREE.MeshBasicMaterial({color:0xffffff, opacity:0})];
        this.scene.remove(this.object);        
        // this.object.geometry = geometry;
        // this.object.materials = [material];
      }

      this.object = new THREE.Mesh(this.geometry, material);
  		this.scene.add(this.object);

      if (this.objectMaterial != 'wireframe') {
        this.object.overdraw = true;
        this.object.doubleSided = true;
      }
      
      this.object.updateMatrix();
    
      this.targetXRotation = 0;
      this.targetYRotation = 0;

      this.sceneLoop();
    }
  }

var STLGeometry = function(stlArray) {
  // log("building geometry...");
	THREE.Geometry.call(this);

	var scope = this;

  // var vertexes = stlArray[0];
  // var normals  = stlArray[1];
  // var faces    = stlArray[2];

  for (var i=0; i<stlArray[0].length; i++) {    
    v(stlArray[0][i][0], stlArray[0][i][1], stlArray[0][i][2]);
  }

  for (var i=0; i<stlArray[1].length; i++) {
    f3(stlArray[1][i][0], stlArray[1][i][1], stlArray[1][i][2]);
  }

  function v(x, y, z) {
    // log("adding vertex: " + x + "," + y + "," + z);
    scope.vertices.push( new THREE.Vector3( x, y, z ) );
  }

  function f3(a, b, c) {
    // log("adding face: " + a + "," + b + "," + c)
    scope.faces.push( new THREE.Face3( a, b, c ) );
  }

  // log("computing centroids...");
  this.computeCentroids();
  // log("computing normals...");
  // this.computeNormals();
	this.computeFaceNormals();
  // log("finished building geometry");

  scope.min_x = 0;
  scope.min_y = 0;
  scope.min_z = 0;
  
  scope.max_x = 0;
  scope.max_y = 0;
  scope.max_z = 0;
  
  for (var v = 0, vl = scope.vertices.length; v < vl; v ++) {
		scope.max_x = Math.max(scope.max_x, scope.vertices[v].x);
		scope.max_y = Math.max(scope.max_y, scope.vertices[v].y);
		scope.max_z = Math.max(scope.max_z, scope.vertices[v].z);
		                                    
		scope.min_x = Math.min(scope.min_x, scope.vertices[v].x);
		scope.min_y = Math.min(scope.min_y, scope.vertices[v].y);
		scope.min_z = Math.min(scope.min_z, scope.vertices[v].z);
  }

  scope.center_x = (scope.max_x + scope.min_x)/2;
  scope.center_y = (scope.max_y + scope.min_y)/2;
  scope.center_z = (scope.max_z + scope.min_z)/2;

  for (var v = 0, vl = scope.vertices.length; v < vl; v ++) {
    scope.vertices[v].x -= scope.center_x;
    scope.vertices[v].y -= scope.center_y;
  }
  scope.max_x -= scope.center_x;
  scope.max_y -= scope.center_y;
                                      
  scope.min_x -= scope.center_x;
  scope.min_y -= scope.center_y;

  scope.center_x = 0;
  scope.center_y = 0;
}

STLGeometry.prototype = new THREE.Geometry();
STLGeometry.prototype.constructor = STLGeometry;

function log(msg) {
  if (console) {
    console.log(msg);
  }
}

/* A facade for the Web Worker API that fakes it in case it's missing. 
Good when web workers aren't supported in the browser, but it's still fast enough, so execution doesn't hang too badly (e.g. Opera 10.5).
By Stefan Wehrmeyer, licensed under MIT
*/

var WorkerFacade;
if(!!window.Worker){
    WorkerFacade = (function(){
        return function(path){
            return new window.Worker(path);
        };
    }());
} else {
    WorkerFacade = (function(){
        var workers = {}, masters = {}, loaded = false;
        var that = function(path){
            var theworker = {}, loaded = false, callings = [];
            theworker.postToWorkerFunction = function(args){
                try{
                    workers[path]({"data":args});
                }catch(err){
                    theworker.onerror(err);
                }
            };
            theworker.postMessage = function(params){
                if(!loaded){
                    callings.push(params);
                    return;
                }
                theworker.postToWorkerFunction(params);
            };
            masters[path] = theworker;
            var scr = document.createElement("SCRIPT");
            scr.src = path;
            scr.type = "text/javascript";
            scr.onload = function(){
                loaded = true;
                while(callings.length > 0){
                    theworker.postToWorkerFunction(callings[0]);
                    callings.shift();
                }
            };
            document.body.appendChild(scr);
            
            var binaryscr = document.createElement("SCRIPT");
            binaryscr.src = thingiurlbase + '/binaryReader.js';
            binaryscr.type = "text/javascript";
            document.body.appendChild(binaryscr);
            
            return theworker;
        };
        that.fake = true;
        that.add = function(pth, worker){
            workers[pth] = worker;
            return function(param){
                masters[pth].onmessage({"data": param});
            };
        };
        that.toString = function(){
            return "FakeWorker('"+path+"')";
        };
        return that;
    }());
}

/* Then just use WorkerFacade instead of Worker (or alias it)

The Worker code must should use a custom function (name it how you want) instead of postMessage.
Put this at the end of the Worker:

if(typeof(window) === "undefined"){
    onmessage = nameOfWorkerFunction;
    customPostMessage = postMessage;
} else {
    customPostMessage = WorkerFacade.add("path/to/thisworker.js", nameOfWorkerFunction);
}

*/
