title: three.js Vector3 to 2D screen coordinate with rotated scene
body: <p>I am trying to position a div such that it is always at the highest point of an object offset left and up a little so it is not right on top of the vertex. You can see that <a href="https://jsfiddle.net/4205qzdm/" rel="nofollow noreferrer">here</a>. Please see the <strong>working with no scene rotation</strong> snippet below if the jsfiddle link no longer works. I can tell you, it works fairly marvellously.</p>
  
  <p>However, for a project at work, I need to rotate the scene, itself. Apparently, this messes up the conversion to 2D screen coordinates. You can view this <a href="https://jsfiddle.net/tz8hcy0e/" rel="nofollow noreferrer">here</a>. You can view the <strong>not working with scene rotation</strong> snippet below as well. As you can see, the label does not update in the vertical ("y-") direction, but it does update "horizontally". This would be because the camera's position does, indeed, change around the x-z plane (phi changes), but it never changes its y-position (theta never changes)--rather the scene is rotated when the mouse is dragged up/down. As I wrote, I need to rotate the scene to achieve the desired affect.</p>
  
  <p>If someone could point me in the right direction or make a quick example in any of the html/js/css snippet sites (like jsfiddle, etc, etc), {,s}he would be a life-saver!</p>
  
  <ul>
  <li><p><em>side-note</em>: I tried (with no success, obviously) to jump through some hoops to convert the x- and y-coordinates for each vertex to a "properly-rotated" position (ie, by multiplying by <code>sin(sceneRotation)</code> and <code>cos(sceneRotation)</code>, but that just made it even worse.</p></li>
  <li><p><em>side-note 2</em>: I also just spent  an hour rotating each object in the scene, but since the objects are actually stored in <code>THREE.Group</code>s, it has the exact same effect.</p></li>
  </ul>
  
  <h2><strong>working with no scene rotation</strong></h2>
  
  <p><em>click <strong>run snippet</strong> below</em></p>
  
  <p><div class="snippet" data-lang="js" data-hide="false" data-console="true" data-babel="false">
  <div class="snippet-code">
  <pre class="snippet-code-js lang-js prettyprint-override"><code>var scene = new THREE.Scene();
  var w = window.innerWidth, h = window.innerHeight;
  var camera = new THREE.PerspectiveCamera(60, w / h, 1, 1000);
  camera.position.set(0, 0, -60);
  var renderer = new THREE.WebGLRenderer({
      alpha: true
  });
  renderer.setSize(window.innerWidth, window.innerHeight);
  renderer.domElement.style.backgroundColor = "#bbbbbb"
  document.body.appendChild(renderer.domElement);
  
  var label = document.getElementById("label");
  var controls = new THREE.OrbitControls(camera, renderer.domElement);
  controls.addEventListener("change", updateLabel);
  
  var geom = new THREE.Geometry();
  geom.vertices.push(new THREE.Vector3(-10, 10, -10));
  geom.vertices.push(new THREE.Vector3(10, 10, -10));
  geom.vertices.push(new THREE.Vector3(10, -10, -10));
  geom.vertices.push(new THREE.Vector3(-10, -10, -10));
  geom.vertices.push(new THREE.Vector3(-10, 10, 10));
  geom.vertices.push(new THREE.Vector3(10, 10, 10));
  geom.vertices.push(new THREE.Vector3(10, -10, 10));
  geom.vertices.push(new THREE.Vector3(-10, -10, 10));
  geom.faces.push(new THREE.Face3(0, 1, 2));
  geom.faces.push(new THREE.Face3(0, 2, 3));
  geom.faces.push(new THREE.Face3(7, 6, 5));
  geom.faces.push(new THREE.Face3(7, 5, 4));
  geom.faces.push(new THREE.Face3(4, 5, 1));
  geom.faces.push(new THREE.Face3(4, 1, 0));
  geom.faces.push(new THREE.Face3(3, 2, 6));
  geom.faces.push(new THREE.Face3(3, 6, 7));
  geom.faces.push(new THREE.Face3(4, 0, 3));
  geom.faces.push(new THREE.Face3(4, 3, 7));
  geom.faces.push(new THREE.Face3(1, 5, 6));
  geom.faces.push(new THREE.Face3(1, 6, 2));
  var mat = new THREE.MeshBasicMaterial({ color: 0x3377dd, transparent: true, opacity: .65, wireframe: false });
  var cube = new THREE.Mesh(geom, mat);
  scene.add(cube);
  var matWire = new THREE.MeshBasicMaterial({ color: 0xffffff, wireframe: true });
  var cube1 = new THREE.Mesh(geom, matWire);
  scene.add(cube1);
  
  render();
  
  function render() {
      requestAnimationFrame(render);
      renderer.render(scene, camera);
  }
  
  function getScreenPosition(position) {
      var vector = new THREE.Vector3( position.x, position.y, position.z );
  
      vector.project(camera);
  
      vector.x = Math.round( (   vector.x + 1 ) * w / 2 );
      vector.y = Math.round( ( - vector.y + 1 ) * h / 2 );
  
      return vector;
  }
  
  function updateLabel() {
      var minY = null, x = null,
       verts = cube.geometry.vertices;
      for (var i = 0, iLen = verts.length; i &lt; iLen; i++) {
          var pos = getScreenPosition(verts[i]);
          if (minY === null || pos.y &lt; minY) {
           minY = pos.y;
              x = pos.x;
          }
      }
      label.style.left = (x - 3) + "px";
      label.style.top = (minY - 28) + "px";
  }</code></pre>
  <pre class="snippet-code-css lang-css prettyprint-override"><code>body {
      overflow: hidden;
      margin: 0;
  }
  
  #label {
      position: absolute;
      left: 20px; top: 20px;
  }</code></pre>
  <pre class="snippet-code-html lang-html prettyprint-override"><code>&lt;div id="label"&gt;label&lt;/div&gt;
  &lt;script src="https://threejs.org/build/three.min.js"&gt;&lt;/script&gt;
  &lt;script src="https://threejs.org/examples/js/controls/OrbitControls.js"&gt;&lt;/script&gt;</code></pre>
  </div>
  </div>
  </p>
  
  <h2><strong>not working with scene rotation</strong></h2>
  
  <p><em>click <strong>run snippet</strong> below</em></p>
  
  <p><div class="snippet" data-lang="js" data-hide="false" data-console="true" data-babel="false">
  <div class="snippet-code">
  <pre class="snippet-code-js lang-js prettyprint-override"><code>/* "globals" */
  /* ~~~~~~~~~ */
  var PI = Math.PI;
  /* camera stuff */
  var lastX, lastY, r = 60, phi = 0, c = new THREE.Vector3(0, 0, 0);
  /* three.js stuff */
  var scene = new THREE.Scene();
  var w = window.innerWidth, h = window.innerHeight;
  var camera = new THREE.PerspectiveCamera(60, w / h, 1, 1000);
  //camera.position.set(0, 0, -60);
  updateCamera();
  var renderer = new THREE.WebGLRenderer({
      alpha: true
  });
  renderer.setSize(window.innerWidth, window.innerHeight);
  renderer.domElement.style.backgroundColor = "#bbbbbb"
  document.body.appendChild(renderer.domElement);
  
  var label = document.getElementById("label");
  //var controls = new THREE.OrbitControls(camera, renderer.domElement);
  //controls.addEventListener("change", updateLabel);
  document.body.addEventListener("mousedown", handleMouseDown);
  document.body.addEventListener("touchstart", handleTouchStart);
  
  var geom = new THREE.Geometry();
  geom.vertices.push(new THREE.Vector3(-10, 10, -10));
  geom.vertices.push(new THREE.Vector3(10, 10, -10));
  geom.vertices.push(new THREE.Vector3(10, -10, -10));
  geom.vertices.push(new THREE.Vector3(-10, -10, -10));
  geom.vertices.push(new THREE.Vector3(-10, 10, 10));
  geom.vertices.push(new THREE.Vector3(10, 10, 10));
  geom.vertices.push(new THREE.Vector3(10, -10, 10));
  geom.vertices.push(new THREE.Vector3(-10, -10, 10));
  geom.faces.push(new THREE.Face3(0, 1, 2));
  geom.faces.push(new THREE.Face3(0, 2, 3));
  geom.faces.push(new THREE.Face3(7, 6, 5));
  geom.faces.push(new THREE.Face3(7, 5, 4));
  geom.faces.push(new THREE.Face3(4, 5, 1));
  geom.faces.push(new THREE.Face3(4, 1, 0));
  geom.faces.push(new THREE.Face3(3, 2, 6));
  geom.faces.push(new THREE.Face3(3, 6, 7));
  geom.faces.push(new THREE.Face3(4, 0, 3));
  geom.faces.push(new THREE.Face3(4, 3, 7));
  geom.faces.push(new THREE.Face3(1, 5, 6));
  geom.faces.push(new THREE.Face3(1, 6, 2));
  var mat = new THREE.MeshBasicMaterial({ color: 0x3377dd, transparent: true, opacity: .65, wireframe: false });
  var cube = new THREE.Mesh(geom, mat);
  //cube.translateX(10);
  scene.add(cube);
  var matWire = new THREE.MeshBasicMaterial({ color: 0xffffff, wireframe: true });
  var cube1 = new THREE.Mesh(geom, matWire);
  //cube1.translateX(10);
  scene.add(cube1);
  
  render();
  
  function render() {
      requestAnimationFrame(render);
      renderer.render(scene, camera);
  }
  
  function getScreenPosition(position) {
      var vector = new THREE.Vector3( position.x, position.y, position.z );
  
      vector.project(camera);
  
      vector.x = Math.round( (   vector.x + 1 ) * w / 2 );
      vector.y = Math.round( ( - vector.y + 1 ) * h / 2 );
  
      return vector;
  }
  
  function updateLabel() {
      var minY = null, x = null,
       verts = cube.geometry.vertices;
      for (var i = 0, iLen = verts.length; i &lt; iLen; i++) {
          var pos = getScreenPosition(verts[i]);
          if (minY === null || pos.y &lt; minY) {
           minY = pos.y;
              x = pos.x;
          }
      }
      label.style.left = (x - 3) + "px";
      label.style.top = (minY - 28) + "px";
  }
  
  function handleMouseDown(ev) {
      ev.preventDefault();
      mouseOrTouchDown(ev.pageX, ev.pageY);
  }
  
  function handleTouchStart(ev) {
      var touches = ev.touches;
      if (touches.length !== 1) {
          return;
      }
      ev.preventDefault();
      mouseOrTouchDown(touches.item(0).pageX, touches.item(0).pageY, true);
  }
  
  function mouseOrTouchDown(downX, downY, touch) {
      if (touch === undefined) { touch = false; }
      lastX = downX;
      lastY = downY;
      if (touch) {
          document.ontouchmove = handleTouchMove;
          document.addEventListener("touchend", function(ev) {
              document.ontouchmove = null;
          });
          document.addEventListener("touchcancel", function(ev) {
              document.removeEventListener("touchmove", handleTouchMove);
          });
      } else {
          document.addEventListener("mousemove", handleMouseMove);
          document.addEventListener("mouseup", function(ev) {
              document.removeEventListener("mousemove", handleMouseMove);
          });
      }
  }
  
  function handleMouseMove(ev) {
      ev.preventDefault();
      mouseOrTouchMove(ev.pageX, ev.pageY);
  }
  
  function handleTouchMove(ev) {
      var touches = ev.touches;
      if (touches.length !== 1) {
          return;
      }
      ev.preventDefault();
      mouseOrTouchMove(touches.item(0).pageX, touches.item(0).pageY);
  }
  
  function mouseOrTouchMove(x, y) {
      var dx = lastX - x, dy = y - lastY; /* change in {x, y} */
  
      phi -= dx / 100;
      if (phi &gt; 2 * PI) {
          phi -= 2 * PI;
      } else if (phi &lt; 0) {
          phi += 2 * PI;
      }
  
      if (phi &lt; PI / 2 || phi &gt; 3 * PI / 2) {
          sign = -1;
      } else {
          sign = 1;
      }
      if (scene.rotation.z + sign * dy / 100 &lt; -PI) {
          scene.rotation.z = -PI;
      } else if (scene.rotation.z + sign * dy / 100 &gt; 0) {
          scene.rotation.z = 0;
      } else {
          scene.rotateZ(sign * dy / 100);
      }
  
      lastX = x;
      lastY = y;
  
      updateCamera();
      updateLabel();
  }
  
  function updateCamera() {
      var z = r * Math.sin(phi); /* new y pos (z-axis) in x-z plane */
      var x = r * Math.cos(phi); /* new x pos (x-axis) in x-z plane */
      camera.position.set(x, 1, z);
      camera.lookAt(c);
  }</code></pre>
  <pre class="snippet-code-css lang-css prettyprint-override"><code>body {
      overflow: hidden;
      margin: 0;
  }
  
  #label {
      position: absolute;
      left: 20px; top: 20px;
  }</code></pre>
  <pre class="snippet-code-html lang-html prettyprint-override"><code>&lt;div id="label"&gt;label&lt;/div&gt;
  &lt;script src="https://threejs.org/build/three.min.js"&gt;&lt;/script&gt;
  &lt;script src="https://threejs.org/examples/js/controls/OrbitControls.js"&gt;&lt;/script&gt;</code></pre>
  </div>
  </div>
  </p>