Three.js Projector and Ray objects
2015-09-07 19:41
567 查看
地址:http://stackoverflow.com/questions/11036106/three-js-projector-and-ray-objects/
I have been trying to work with the Projector and Ray classes in order to do some collision detection demos. I have started just trying to use the mouse to select objects or to drag them. I have looked at examples that use the objects, but none of them seem
to have comments explaining what exactly some of the methods of Projector and Ray are doing. I have a couple questions that I am hoping will be easy for someone to answer.
What exactly is happening and what is the difference between Projector.projectVector() and Projector.unprojectVector()? I notice that it seems in all the examples using both projector and ray objects
the unproject method is called before the ray is created. When would you use projectVector?
I am using the following code in this demo to
spin the cube when dragged on with the mouse. Can someone explain in simple terms what exactly is happening when I unproject with the mouse3D and camera and then create the Ray. Does the ray depend on the call
to unprojectVector()
Answer1:
Basically, you need to project from the 3D world space and the 2D screen space.
Renderers use
translating 3D points to the 2D screen.
basically for doing the inverse, unprojecting 2D points into the 3D world. For both methods you pass the camera you're viewing the scene through.
So, in this code you're creating a normalised vector in 2D space. To be honest, I was never too sure about the
Then, this code uses the camera projection matrix to transform it to our 3D world space.
With the mouse3D point converted into the 3D space, we can now use it for getting the direction and then use the camera position to throw a ray from.
Answer2:
I found that I needed to go a bit deeper under the surface to work outside of the scope of the sample code (such as having a canvas that does not fill the screen or having additional effects). I wrote a blog post about it here.
This is a shortened version, but should cover pretty much everything I found.
The following code (similar to that already provided by @mrdoob) will change the color of a cube when clicked:
With the more recent three.js releases (around r55 and later), you can use pickingRay which simplifies things even further so that this becomes:
Let's stick with the old approach as it gives more insight into what is happening under the hood. You can see this working here,
simply click on the cube to change its colour.
the x coordinate of the click position. Dividing by
the position of the click in proportion of the full window width. Basically, this is translating from screen coordinates that start at (0,0) at the top left through to (
at the bottom right, to the cartesian coordinates with center (0,0) and ranging from (-1,-1) to (1,1) as shown below:
Note that z has a value of 0.5. I won't go into too much detail about the z value at this point except to say that this is the depth of the point away from the camera that we are projecting into 3D space along the z axis. More on this later.
Next:
If you look at the three.js code you will see that this is really an inversion of the projection matrix from the 3D world to the camera. Bear in mind that in order to get from 3D world coordinates to a projection on the screen, the 3D world needs to be projected
onto the 2D surface of the camera (which is what you see on your screen). We are basically doing the inverse.
Note that mouse3D will now contain this unprojected value. This is the position of a point in 3D space along the ray/trajectory that we are interested in. The exact point depends on the z value (we will see this later).
At this point, it may be useful to have a look at the following image:
The point that we have just calculated (mouse3D) is shown by the green dot. Note that the size of the dots are purely illustrative, they have no bearing on the size of the camera or mouse3D point. We are more interested in the coordinates at the center of the
dots.
Now, we don't just want a single point in 3D space, but instead we want a ray/trajectory (shown by the black dots) so that we can determine whether an object is positioned along this ray/trajectory. Note that the points shown along the ray are just arbitrary
points, the ray is a direction from the camera, not a set of points.
Fortunately, because we a have a point along the ray and we know that the trajectory must pass from the camera to this point, we can determine the direction of the ray. Therefore, the next step is to subtract the camera position from the mouse3D position, this
will give a directional vector rather than just a single point:
We now have a direction from the camera to this point in 3D space (mouse3D now contains this direction). This is then turned into a unit vector by normalizing it.
The next step is to create a ray (Raycaster) starting from the camera position and using the direction (mouse3D) to cast the ray:
The rest of the code determines whether the objects in 3D space are intersected by the ray or not. Happily it is all taken care of us behind the scenes using
OK, so let's look at a demo from my site here that
shows these rays being cast in 3D space. When you click anywhere, the camera rotates around the object to show you how the ray is cast. Note that when the camera returns to its original position, you only see a single dot. This is because all the other dots
are along the line of the projection and therefore blocked from view by the front dot. This is similar to when you look down the line of an arrow pointing directly away from you - all that you see is the base. Of course, the same applies when looking down
the line of an arrow that is travelling directly towards you (you only see the head), which is generally a bad situation to be in.
Let's take another look at that z coordinate. Refer to this
demo as you read through this section and experiment with different values for z.
OK, lets take another look at this function:
We chose 0.5 as the value. I mentioned earlier that the z coordinate dictates the depth of the projection into 3D. So, let's have a look at different values for z to see what effect it has. To do this, I have placed a blue dot where the camera is, and a line
of green dots from the camera to the unprojected position. Then, after the intersections have been calculated, I move the camera back and to the side to show the ray. Best seen with a few examples.
First, a z value of 0.5:
Note the green line of dots from the camera (blue dot) to the unprojected value (the coordinate in 3D space). This is like the barrel of a gun, pointing in the direction that they ray should be cast. The green line essentially represents the direction that
is calculated before being normalised.
OK, let's try a value of 0.9:
As you can see, the green line has now extended further into 3D space. 0.99 extends even further.
I do not know if there is any importance as to how big the value of z is. It seems that a bigger value would be more precise (like a longer gun barrel), but since we are calculating the direction, even a short distance should be pretty accurate. The examples
that I have seen use 0.5, so that is what I will stick with unless told otherwise.
Now that we know a bit more about what is going on, we can figure out what the values should be when the canvas does not fill the window and is positioned on the page. Say, for example, that:
the div containing the three.js canvas is offsetX from the left and offsetY from the top of the screen.
the canvas has a width equal to viewWidth and height equal to viewHeight.
The code would then be:
Basically, what we are doing is calculating the position of the mouse click relative to the canvas (for x:
similar to when the canvas filled the window.
That's it, hopefully it helps.
I have been trying to work with the Projector and Ray classes in order to do some collision detection demos. I have started just trying to use the mouse to select objects or to drag them. I have looked at examples that use the objects, but none of them seem
to have comments explaining what exactly some of the methods of Projector and Ray are doing. I have a couple questions that I am hoping will be easy for someone to answer.
What exactly is happening and what is the difference between Projector.projectVector() and Projector.unprojectVector()? I notice that it seems in all the examples using both projector and ray objects
the unproject method is called before the ray is created. When would you use projectVector?
I am using the following code in this demo to
spin the cube when dragged on with the mouse. Can someone explain in simple terms what exactly is happening when I unproject with the mouse3D and camera and then create the Ray. Does the ray depend on the call
to unprojectVector()
/** Event fired when the mouse button is pressed down */ function onDocumentMouseDown(event) { event.preventDefault(); mouseDown = true; mouse3D.x = mouse2D.x = mouseDown2D.x = (event.clientX / window.innerWidth) * 2 - 1; mouse3D.y = mouse2D.y = mouseDown2D.y = -(event.clientY / window.innerHeight) * 2 + 1; mouse3D.z = 0.5; /** Project from camera through the mouse and create a ray */ projector.unprojectVector(mouse3D, camera); var ray = new THREE.Ray(camera.position, mouse3D.subSelf(camera.position).normalize()); var intersects = ray.intersectObject(crateMesh); // store intersecting objects if (intersects.length > 0) { SELECTED = intersects[0].object; var intersects = ray.intersectObject(plane); } } /** This event handler is only fired after the mouse down event and before the mouse up event and only when the mouse moves */ function onDocumentMouseMove(event) { event.preventDefault(); mouse3D.x = mouse2D.x = (event.clientX / window.innerWidth) * 2 - 1; mouse3D.y = mouse2D.y = -(event.clientY / window.innerHeight) * 2 + 1; mouse3D.z = 0.5; projector.unprojectVector(mouse3D, camera); var ray = new THREE.Ray(camera.position, mouse3D.subSelf(camera.position).normalize()); if (SELECTED) { var intersects = ray.intersectObject(plane); dragVector.sub(mouse2D, mouseDown2D); return; } var intersects = ray.intersectObject(crateMesh); if (intersects.length > 0) { if (INTERSECTED != intersects[0].object) { INTERSECTED = intersects[0].object; } } else { INTERSECTED = null; } } /** Removes event listeners when the mouse button is let go */ function onDocumentMouseUp(event) { event.preventDefault(); /** Update mouse position */ mouse3D.x = mouse2D.x = (event.clientX / window.innerWidth) * 2 - 1; mouse3D.y = mouse2D.y = -(event.clientY / window.innerHeight) * 2 + 1; mouse3D.z = 0.5; if (INTERSECTED) { SELECTED = null; } mouseDown = false; dragVector.set(0, 0); } /** Removes event listeners if the mouse runs off the renderer */ function onDocumentMouseOut(event) { event.preventDefault(); if (INTERSECTED) { plane.position.copy(INTERSECTED.position); SELECTED = null; } mouseDown = false; dragVector.set(0, 0); }
Answer1:
Basically, you need to project from the 3D world space and the 2D screen space.
Renderers use
projectVectorfor
translating 3D points to the 2D screen.
unprojectVectoris
basically for doing the inverse, unprojecting 2D points into the 3D world. For both methods you pass the camera you're viewing the scene through.
So, in this code you're creating a normalised vector in 2D space. To be honest, I was never too sure about the
z = 0.5logic.
mouse3D.x = (event.clientX / window.innerWidth) * 2 - 1; mouse3D.y = -(event.clientY / window.innerHeight) * 2 + 1; mouse3D.z = 0.5;
Then, this code uses the camera projection matrix to transform it to our 3D world space.
projector.unprojectVector(mouse3D, camera);
With the mouse3D point converted into the 3D space, we can now use it for getting the direction and then use the camera position to throw a ray from.
var ray = new THREE.Ray(camera.position, mouse3D.subSelf(camera.position).normalize()); var intersects = ray.intersectObject(plane);
Answer2:
I found that I needed to go a bit deeper under the surface to work outside of the scope of the sample code (such as having a canvas that does not fill the screen or having additional effects). I wrote a blog post about it here.
This is a shortened version, but should cover pretty much everything I found.
How to do it
The following code (similar to that already provided by @mrdoob) will change the color of a cube when clicked:var mouse3D = new THREE.Vector3( ( event.clientX / window.innerWidth ) * 2 - 1, //x -( event.clientY / window.innerHeight ) * 2 + 1, //y 0.5 ); //z projector.unprojectVector( mouse3D, camera ); mouse3D.sub( camera.position ); mouse3D.normalize(); var raycaster = new THREE.Raycaster( camera.position, mouse3D ); var intersects = raycaster.intersectObjects( objects ); // Change color if hit block if ( intersects.length > 0 ) { intersects[ 0 ].object.material.color.setHex( Math.random() * 0xffffff ); }
With the more recent three.js releases (around r55 and later), you can use pickingRay which simplifies things even further so that this becomes:
var mouse3D = new THREE.Vector3( ( event.clientX / window.innerWidth ) * 2 - 1, //x -( event.clientY / window.innerHeight ) * 2 + 1, //y 0.5 ); //z var raycaster = projector.pickingRay( mouse3D.clone(), camera ); var intersects = raycaster.intersectObjects( objects ); // Change color if hit block if ( intersects.length > 0 ) { intersects[ 0 ].object.material.color.setHex( Math.random() * 0xffffff ); }
Let's stick with the old approach as it gives more insight into what is happening under the hood. You can see this working here,
simply click on the cube to change its colour.
What's happening?
var mouse3D = new THREE.Vector3( ( event.clientX / window.innerWidth ) * 2 - 1, //x -( event.clientY / window.innerHeight ) * 2 + 1, //y 0.5 ); //z
event.clientXis
the x coordinate of the click position. Dividing by
window.innerWidthgives
the position of the click in proportion of the full window width. Basically, this is translating from screen coordinates that start at (0,0) at the top left through to (
window.innerWidth,
window.innerHeight)
at the bottom right, to the cartesian coordinates with center (0,0) and ranging from (-1,-1) to (1,1) as shown below:
Note that z has a value of 0.5. I won't go into too much detail about the z value at this point except to say that this is the depth of the point away from the camera that we are projecting into 3D space along the z axis. More on this later.
Next:
projector.unprojectVector( mouse3D, camera );
If you look at the three.js code you will see that this is really an inversion of the projection matrix from the 3D world to the camera. Bear in mind that in order to get from 3D world coordinates to a projection on the screen, the 3D world needs to be projected
onto the 2D surface of the camera (which is what you see on your screen). We are basically doing the inverse.
Note that mouse3D will now contain this unprojected value. This is the position of a point in 3D space along the ray/trajectory that we are interested in. The exact point depends on the z value (we will see this later).
At this point, it may be useful to have a look at the following image:
The point that we have just calculated (mouse3D) is shown by the green dot. Note that the size of the dots are purely illustrative, they have no bearing on the size of the camera or mouse3D point. We are more interested in the coordinates at the center of the
dots.
Now, we don't just want a single point in 3D space, but instead we want a ray/trajectory (shown by the black dots) so that we can determine whether an object is positioned along this ray/trajectory. Note that the points shown along the ray are just arbitrary
points, the ray is a direction from the camera, not a set of points.
Fortunately, because we a have a point along the ray and we know that the trajectory must pass from the camera to this point, we can determine the direction of the ray. Therefore, the next step is to subtract the camera position from the mouse3D position, this
will give a directional vector rather than just a single point:
mouse3D.sub( camera.position ); mouse3D.normalize();
We now have a direction from the camera to this point in 3D space (mouse3D now contains this direction). This is then turned into a unit vector by normalizing it.
The next step is to create a ray (Raycaster) starting from the camera position and using the direction (mouse3D) to cast the ray:
var raycaster = new THREE.Raycaster( camera.position, mouse3D );
The rest of the code determines whether the objects in 3D space are intersected by the ray or not. Happily it is all taken care of us behind the scenes using
intersectsObjects.
The Demo
OK, so let's look at a demo from my site here thatshows these rays being cast in 3D space. When you click anywhere, the camera rotates around the object to show you how the ray is cast. Note that when the camera returns to its original position, you only see a single dot. This is because all the other dots
are along the line of the projection and therefore blocked from view by the front dot. This is similar to when you look down the line of an arrow pointing directly away from you - all that you see is the base. Of course, the same applies when looking down
the line of an arrow that is travelling directly towards you (you only see the head), which is generally a bad situation to be in.
The z coordinate
Let's take another look at that z coordinate. Refer to thisdemo as you read through this section and experiment with different values for z.
OK, lets take another look at this function:
var mouse3D = new THREE.Vector3( ( event.clientX / window.innerWidth ) * 2 - 1, //x -( event.clientY / window.innerHeight ) * 2 + 1, //y 0.5 ); //z
We chose 0.5 as the value. I mentioned earlier that the z coordinate dictates the depth of the projection into 3D. So, let's have a look at different values for z to see what effect it has. To do this, I have placed a blue dot where the camera is, and a line
of green dots from the camera to the unprojected position. Then, after the intersections have been calculated, I move the camera back and to the side to show the ray. Best seen with a few examples.
First, a z value of 0.5:
Note the green line of dots from the camera (blue dot) to the unprojected value (the coordinate in 3D space). This is like the barrel of a gun, pointing in the direction that they ray should be cast. The green line essentially represents the direction that
is calculated before being normalised.
OK, let's try a value of 0.9:
As you can see, the green line has now extended further into 3D space. 0.99 extends even further.
I do not know if there is any importance as to how big the value of z is. It seems that a bigger value would be more precise (like a longer gun barrel), but since we are calculating the direction, even a short distance should be pretty accurate. The examples
that I have seen use 0.5, so that is what I will stick with unless told otherwise.
Projection when the canvas is not full screen
Now that we know a bit more about what is going on, we can figure out what the values should be when the canvas does not fill the window and is positioned on the page. Say, for example, that:the div containing the three.js canvas is offsetX from the left and offsetY from the top of the screen.
the canvas has a width equal to viewWidth and height equal to viewHeight.
The code would then be:
var mouse3D = new THREE.Vector3( ( event.clientX - offsetX ) / viewWidth * 2 - 1, -( event.clientY - offsetY ) / viewHeight * 2 + 1, 0.5 );
Basically, what we are doing is calculating the position of the mouse click relative to the canvas (for x:
event.clientX - offsetX). Then we determine proportionally where the click occurred (for x:
/viewWidth)
similar to when the canvas filled the window.
That's it, hopefully it helps.
相关文章推荐
- AttributeError: 'module' object has no attribute 'HAVE_DECL_MPZ_POWM_SEC'
- iOS学习篇章1--Objective-C基础语法
- [Object-C] 关于UIView的阴影
- ObjectiveC开发教程--如何判断字符串是否为空的方法
- [iOS]Objective-C 第一节课
- spring jack 使用自定义的objectMapper。或者在xml配置objectMapper参数
- objective-c中的协议和类别
- 黑马程序员——Objective-C Foundation框架中的NSDirctionary类以及NSMutableDirctionary类
- 黑马程序员——Objective-C Foundation框架中的NSMutableString对象
- 黑马程序员——Objective-C Foundation框架中的NSString对象
- 黑马程序员——Objective-C Foundation框架中的NSObject对象
- Elasticsearch中的根对象(Root Object)
- 黑马程序员——Objective-C之特殊语法总结
- objective-c 中随机数的用法 (3种:arc4random() 、random()、CCRANDOM_0_1() )
- [__NSCFString countByEnumeratingWithState:objects:count:]: unrecognized selector sent to instance 0x17deba00
- Objective-C中的@property和@synthesize用法
- Effective Objective C 2.0
- 【转】Objective-C 与 Runtime:为什么是这样?
- JSONObject与JSONArray的使用
- error LNK2001: unresolved external symbol "public: virtual struct QMetaObject