1
votes

I'm working on a project in three-js. I am trying to project 2D coordinates onto a sphere in the world.

The below code works perfectly when I bound the function to the mousedown event:

function project(point){
  var x = 2 * (point[0] - width/2) / width;
  var y = 2 * (point[1] - height/2) / height;
  var l = new THREE.Vector3(x, y, 0.5);
  l.unproject(camera);
  l.sub(camera.position).normalize(); // ray / direction of click on world coordinates

  // Below is code for Ray Sphere Intersection (I'm certain this works)
  var omc = camera.position.clone().sub(sphere.position);
  var ldomc = l.dot(omc);
  var d = -ldomc - Math.sqrt(ldomc*ldomc - omc.lengthSq() + radius*radius);
  if (d == d){ // if there was an intersection
    return camera.position.clone().add(l.multiplyScalar(d));
  }
  return null; // If no intersection was found
}

However, when I tried to use the above function in the following code the function only returned null (there were no intersection found).

get_random_point(scale){ // scale is the projected radius (in pixels) of the sphere
  while (true){ // continue until a point works
    var a = Math.random();
    var b = Math.sqrt(Math.random());
    var x = width/2 + scale * b * Math.cos(2 * Math.PI * a);
    var y = height/2 + scale * b * Math.sin(2 * Math.PI * a);
    if (width > x && x >= 0 && height > y && y >= 0){
      var projp = project([x,y]);
      if (projp != null){
        return toSpherical(projp); // Return spherical coordinates of point
      }
    }
  }
}

The above code is called ~100 times each animation cycle to pick a random point visible to the camera on the surface of the sphere.

The problem only occurs once the camera is moved. I control the camera using the mouse to make it orbit the sphere.

I managed to dump all of the variables declared inside project to the javascript console when this happens. I then tested the project function in a different tab with the same camera pose and point and found that the calculated ray was incorrect.

Here are the functions for camera control:

d3.select("body").on("mousedown", function(event){
  m0 = d3.mouse(this);
});

d3.select("body").on("mousemove", function(event){
  if (m0 != null){
    var m1 = d3.mouse(this);
    var mv = [m1[0] - m0[0], m1[1] - m0[1]];
    theta -= mv[0]*0.25/180.0*Math.PI;
    phi += mv[1]*0.25/180.0*Math.PI*camera.up.y;
    if (phi > Math.PI/2){ phi = Math.PI - phi; camera.up.y *= -1; theta += Math.PI}
    if (-Math.PI/2 > phi){ phi = -Math.PI -phi; camera.up.y *= -1; theta += Math.PI}
    while (theta >= 2 * Math.PI){ theta -= 2 * Math.PI; }
    while ( 0 > theta){ theta += 2 * Math.PI; }
    camera.position.x = r * Math.sin(theta) * Math.cos(phi);
    camera.position.y = r * Math.sin(phi);
    camera.position.z = r * Math.cos(theta) * Math.cos(phi);
    camera.lookAt(new THREE.Vector3(0, 0, 0));
    m0 = m1;
  }
});

d3.select("body").on("mouseup", function(event){
  m0 = null;
}).on("mouseleave", function(event){ m0 = null; });

function Zoom(event){
  var movement;
  if (event.wheelDelta){
    movement = event.wheelDelta/120; // wheelDelta is in increments of 120
  } else {
    movement = event.deltaY/-3; // DeltaY is in increments of 3;
  }
  r -= 0.05 * movement;
  if (0.9 > r){
    r = 0.9;
  } else if (r > 2.5){
    r = 2.5;
  }
  camera.position.x = r * Math.sin(theta) * Math.cos(phi);
  camera.position.y = r * Math.sin(phi);
  camera.position.z = r * Math.cos(theta) * Math.cos(phi);
  camera.lookAt(new THREE.Vector3(0, 0, 0));
}

I can't seem to figure out why the project function would work for the same point and camera pose when not being called from the animation loop.

My guess is that the camera is modified during mouse motion call back but one of the camera's matrices aren't updated causing the Three.Vector3.unproject() function to not give the desired output.

EDIT: I was able to fix the issue by moving the animation directly after the render call. This makes me almost certain it was a problem with not having the camera matrices not being updated. I know how to update the camera projection matrix. How can I force the camera's matrix world to update?

1
I can't debug your code for you, but try calling camera.updateMatrixWorld() right after you modify the camera positions. Also, in tight loops, avoid new and clone(). Create one instance and reuse it using the copy() method, instead.WestLangley
Hello, Thanks for the response. I'll try the updateMatrixWorld method. Also good suggestion on not using the new/clone() in tight loops. I should avoid the additional cost of creating new object instances repeatedly.Timothy Murphy

1 Answers

2
votes

In three.js, the renderer calls camera.updateMatrixWorld() for you every render loop.

If you modify the camera position, rotation, or quaternion, and you then use a method that relies on camera.matrixWorld, and if that method does not update camera.matrixWorld itself, you have to update the matrix manually.

camera.updateMatrixWorld();

three.js r.71