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?
camera.updateMatrixWorld()
right after you modify the camera positions. Also, in tight loops, avoidnew
andclone()
. Create one instance and reuse it using thecopy()
method, instead. – WestLangley