So, I have a plane, and I got raycast set up. What I want to do is get the coordinate of the intersection on the plane, thanks.
(Note: the color change is just to test if the raycast was actually working.)
Here is my code :
raycaster.setFromCamera( mouse, camera );
//calculate objects intersecting the picking ray
var intersects = raycaster.intersectObjects( scene.children );
for ( var i = 0; i < intersects.length; i++ ) {
intersects[ i ].object.material.color.set( 0xff0000 );
}
Cheers, Keegan.
When using Raycaster, the value intersects[ i ].point is a Vector3() representing the world coordinates of the point of intersection.
Inspect intersects[ i ] in the console to see what else is returned.
three.js r.72
Related
I'd like to be able to catch the faces of an object in the radius of a circular cursor (like in painting/photoshop).
I'll show you what is it for https://jsfiddle.net/Shaggisu/w7ufmutr/9/
I'd want be able to selct not only the single face that in the moment intersects with the mouse point but all faces that might be in the circular radius, I tried to uplod some image for that cursor but cant really make it work with external files in jsfiddle.
My question is, if is there some standard method of achieving multiple selection/intersection in a specified radius or should I devised some code that would for example reiterate on suroundin faces around the mouse point in specific moment.
I'm still quite new to three.js so I would ask for some directions to go with it, and especialy if there are some solid ways to achieve, any tip would be helpful too.
var brushTexture = THREE.ImageUtils.loadTexture( '/cur_circle.png' );
var brushMaterial = new THREE.SpriteMaterial( { map: brushTexture, useScreenCoordinates: true, alignment: THREE.SpriteAlignment.center } );
brushSprite = new THREE.Sprite( brushMaterial );
brushSprite.scale.set( 32, 32, 1.0 );
brushSprite.position.set( 50, 50, 0 );
scene.add( brushSprite );
//////////////////////////////////////////////////////////////////////
// initialize object to perform world/screen calculations
projector = new THREE.Projector();
// when the mouse moves, call the given function
document.addEventListener( 'mousedown', onDocumentMouseDown, false );
}
function onDocumentMouseDown( event )
{
// the following line would stop any other event handler from firing
// (such as the mouse's TrackballControls)
event.preventDefault();
console.log("Click.");
// update the mouse variable
mouse.x = ( event.clientX / window.innerWidth ) * 2 - 1;
mouse.y = - ( event.clientY / window.innerHeight ) * 2 + 1;
// find intersections
// create a Ray with origin at the mouse position
// and direction into the scene (camera direction)
var vector = new THREE.Vector3( mouse.x, mouse.y, 1 );
projector.unprojectVector( vector, camera );
var ray = new THREE.Raycaster( camera.position, vector.sub( camera.position ).normalize() );
// create an array containing all objects in the scene with which the ray intersects
var intersects = ray.intersectObjects( targetList );
// if there is one (or more) intersections
if ( intersects.length > 0 )
{
controls.enabled = false; // stops camera rotation
console.log("Hit # " + toString( intersects[0].point ) );
// change the color of the closest face.
intersects[ 0 ].face.color.setRGB( 0.8 * Math.random() + 0.2, 0, 0 );
intersects[ 0 ].object.geometry.colorsNeedUpdate = true;
document.addEventListener( 'mousemove', onDocumentMouseMove, false );
}
}
function onDocumentMouseMove( event){
event.preventDefault();
mouse.x = ( event.clientX / window.innerWidth ) * 2 - 1;
mouse.y = - ( event.clientY / window.innerHeight ) * 2 + 1;
brushSprite.position.set( event.clientX, event.clientY, 0);
// find intersections
// create a Ray with origin at the mouse position
// and direction into the scene (camera direction)
var vector = new THREE.Vector3( mouse.x, mouse.y, 1 );
projector.unprojectVector( vector, camera );
var ray = new THREE.Raycaster( camera.position, vector.sub( camera.position ).normalize() );
// create an array containing all objects in the scene with which the ray intersects
var intersects = ray.intersectObjects( targetList );
// if there is one (or more) intersections
if ( intersects.length > 0 )
{
console.log("Hit # " + toString( intersects[0].point ) );
// change the color of the closest face.
intersects[ 0 ].face.color.setRGB( 0.8 * Math.random() + 0.2, 0, 0 );
intersects[ 0 ].object.geometry.colorsNeedUpdate = true;
}
document.addEventListener( 'mouseup', onDocumentMouseUp, false );
}
function onDocumentMouseUp( event){
event.preventDefault();
document.removeEventListener( "mousemove", onDocumentMouseMove);
controls.enabled = true;
}
The code is modified version of the stemkoskis github that I used for practice.
I have already extended it somewhat for camera management in intersection events and continous selection, but the selection of multiple faces in a radius is what interests me now the most.
You can do this in javascript, modifying vertex color, like you do it in your sample but you will be quickly limited by the number of polygon.
That said, consider your brush like a cone, which start from the Ray.origin and extend in Ray.direction. The radius of the cone is driven by the radius of your brush.
Iterate over each vertices.
For each, get the minimum distance between the vertex to the Ray line.
Get the radius of the brush/cone based on the distance between this vertex and the Ray.origin
If the minimum distance is inferior to the cone radius, your vertex is "in". You can also handle the distance to create a smooth brush.
It should looks like this, it kind of pseudo code, you may need to adapt to ThreeJs Math lib:
// Important, Ray origin and direction must be defined in the same space a vertices positions
// You may need to transform ray origin and direction in object local space.
// get the length of Ray.direction
// may be useless if 'direction' is normalized
var rayDirLenSq = ray.direction.length();
rayDirLenSq *= rayDirLenSq;
var brushRadius = 10.0;
for( var i=0;i< vertices.length;i++){
// get the vertex
var v = vertices[i];
var vdir = v.sub( ray.origin );
var dot = vdir.dot( ray.direction ) / rayDirLenSq;
if( dot < 0 ){
// handle vertices behind the camera if needed
}
// v2 : projection of the vertex onto ray line
var v2 = ray.direction.clone().multiplyScalar( dot );
// v3 : projection -> vertex
var v3 = vdir.subtract( v2 )
// dist is the distance between the vertex and the ray line
var dist = v3.length()
// 0 when vertex is at the brush border
// 1 when vertex is in the brush center
var paintingFactor = Math.max(0.0, 1.0 - dist/brushRadius )
}
Depending of what you want, you can store the painting factor of each vertices to get a mean factor per faces. Or you can modify vertex color of each vertices independantly to get gradients on your faces...
I didn't test the code, it may contain some mistakes :)
A more advanced method
You could also use a texture to paint on. You will get rid of vertex (and javascript) limitations. You will be able to paint with textured brushes, and have detail inside a triangle (no more vertex color).
The principle :
You need UVs datas and a texture + FBO for each of your meshes.
In a prepass, for each mesh, render it to it's Fbo in it's uvs coords
gl_Position = vec4( UVs*2.0-1.0, 0.0, 1.0 );
Provide the worldSpace vertex position to fragment shader, so you can access the world space position of each pixel of the object texture.
vVertexPosition = modelMatrix * aPosition;
With vVertexPosition in your fragment shader, you can then use the same method as the javascript one to get the brushFactor of each pixels of your mesh.
You can even project this world space pixel position in a custom projection matrix based on the Ray to get the uvs coordinate of the pixel in a brush texture, and paint with textured brush.
I'm building a shooter in three.js in which spheres appear that the user must shoot.
After shooting a sphere, the object disappears and another sphere pops up. I'm using Pointer Lock Controls.
I detect when a sphere gets shot using:
function onDocumentMouseDown( event ) {
var mouse3D = new THREE.Vector3();
var raycaster = new THREE.Raycaster();
mouse3D.normalize();
controls.getDirection( mouse3D );
raycaster.set( controls.getObject().position, mouse3D );
var intersects = raycaster.intersectObjects( objects );
// Change color if hit sphere
if ( intersects.length > 0 ) {
intersects[ 0 ].object.material.color.setHex( Math.random() * 0xffffff );
//hide shpere
object.traverse(function(child){child.visible = false;});
update_sphereplace();
}
}
This works perfectly, but now I want to integrate an accuracy feature.
How could I track the accuracy based on the center of the sphere? So for example hitting the center should give me an accuracy of 100. Hitting the sphere relatively far from the center should give an accuracy near to 0 (but not 0, since 0 would be missing the sphere).
You need to know by how much the ray misses the center of the sphere mesh.
var missDistance = raycaster.ray.distanceToPoint( intersects[ 0 ].object.position );
You then need to map that value to a score -- something like:
var score = 100 * Math.max( 1 - ( missDistance / sphereRadius ), 0 );
three.js r.76
I used STLLoader to load an stl onto a threeJS scene returning a BufferGeometry.
I then used
myMesh.position.set( x,y,z )
myMesh.rotation.setFromQuaternion ( quaternion , 'XYZ');
to translate the geometry. This effectively changes the
myMesh.position
myMesh.quaternion
Translation is happening in the scene and all works well.
I expected that the
myMesh.geometry.attributes.position.array
would be different before and after the translation - but it remained identical. I want to extract the new veritces from the buffergeometry after translation.
I tried to call
myMesh.geometry.dynamic = true;
myMesh.geometry.attributes.position.needsUpdate = true;
in the render loop but no luck as I haven't updated the vertices explicity.
You want to get the world position of a mesh's geometry, taking into consideration the mesh's transform matrix, mesh.matrix. Also, your mesh geometry is THREE.BufferGeometry.
Here is the pattern to follow:
mesh = new THREE.Mesh( geometry, material );
mesh.position.set( 10, 10, 10 );
mesh.rotation.set( - Math.PI / 2, 0, 0 );
mesh.scale.set( 1, 1, 1 );
scene.add( mesh );
mesh.updateMatrix(); // make sure the mesh's matrix is updated
var vec = new THREE.Vector3();
var attribute = mesh.geometry.attributes.position; // we want the position data
var index = 1; // index is zero-based, so this the the 2nd vertex
vec.fromAttribute( attribute, index ); // extract the x,y,z coordinates
vec.applyMatrix4( mesh.matrix ); // apply the mesh's matrix transform
three.js r.71
I'm trying to use THREE.js and been looking at some examples, Voxel Painter exmaple
I'm trying to get it so that every time you click to create a new cube the roll over mesh will always move on top of the cube just pasted rather than being at the point of intersecting of the current mouse position...
All of the source code can be viewed from the link but I believe what I'm trying to do has something to do with this...
You click the mouse to add a Voxel, when onMouseDown() function is active it will check if current mouse position is intersecting with the plane and if CTRL button has been pressed for either a new cube or delete a cube.
function onDocumentMouseDown( event ) {
event.preventDefault();
var intersects = raycaster.intersectObjects( scene.children );
if ( intersects.length > 0 ) {
intersector = getRealIntersector( intersects );
// delete cube
if ( isCtrlDown ) {
if ( intersector.object != plane ) {
scene.remove( intersector.object );
}
}
// create cube
else {
intersector = getRealIntersector( intersects);
setVoxelPosition( intersector );
var voxel = new THREE.Mesh( cubeGeo, cubeMaterial );
voxel.position.copy( voxelPosition );
voxel.matrixAutoUpdate = false;
voxel.updateMatrix();
scene.add( voxel );
}
}
}
When creating a new cube I believe THREE.js grabs the current point where the mouse intersects intersector = getRealIntersector( intersects); and then sets the new Voxel position with the function setVoxelPosition( intersector ); with the intersect point being passed in.
This is the setVoxelPosition function
function setVoxelPosition( intersector ) {
normalMatrix.getNormalMatrix( intersector.object.matrixWorld );
tmpVec.copy( intersector.face.normal );
tmpVec.applyMatrix3( normalMatrix ).normalize();
voxelPosition.addVectors( intersector.point, tmpVec );
voxelPosition.x = Math.floor( voxelPosition.x / 50 ) * 50 + 25;
voxelPosition.y = Math.floor( voxelPosition.y / 50 ) * 50 + 25;
voxelPosition.z = Math.floor( voxelPosition.z / 50 ) * 50 + 25;
}
and the render loop
function render() {
if ( isShiftDown )
theta += mouse2D.x * 1.5;
raycaster = projector.pickingRay( mouse2D.clone(), camera )
var intersects = raycaster.intersectObjects( scene.children );
if ( intersects.length > 0 ) {
intersector = getRealIntersector( intersects );
if ( intersector ) {
setVoxelPosition( intersector );
rollOverMesh.position = voxelPosition;
}
}
camera.position.x = 1400 * Math.sin( THREE.Math.degToRad( theta ) );
camera.position.z = 1400 * Math.cos( THREE.Math.degToRad( theta ) );
camera.lookAt( scene.position );
renderer.render( scene, camera );
}
I have tried to pass in different values into setVoxelPosition( intersector ) but I can't seem to get it right..
Could someone please point me in the right direction?
Thanks.
There are several ways of doing this. I'm not going to sugar coat this answer because frankly, reverse engineering this code will do you some good. I will say, having worked with this voxel code myself, it's important you understand what's happening when you click the mouse button and create a new voxel box.
You're correct in understanding that, this function is in fact taking the current mouse position and creating the box. When the click is complete and the box has been made, the process starts over so the program again looks to where the mouse is and places the Ghost box. In this case the Ghost box is not on top of the previously made box, so you'd have to move the mouse up manually a few pixels to get it there.
Rather than fool around with the setVoxelPosition function directly, I'd recommend you 'temporarly change the x,y,z position of the ghost in relation to the matrix intersect mouse position of your computer. Upon a successful click, increase the matrixIntersect.x .y .z properties of this matrixIntersects object, increasing these values only a little so you get the 'box-on-top' effect you want directly after a click. Remember to change them back when the users mouse moves off the object otherwise the Ghost box will no longer be directly under the mouse and things can get messy fast if these properties grow after every click.
I have building project with three.js... canvas where you can drag object
and play with the camera view as well... there is a famous example- "Draggable Cubes",
well my project is pretty similar.
On my project there are 3 main events: mouseup /mousedown/ mousemove...
Well everything was ok....but now I'm trying to run this code on iPhone,changing my events
with touchstart / touchmove / touchend...
The moving object function seems to work fine, but when I'm trying to select the object by clicking him,
it's always the same object that been selected... and not the one I'm pointing on....
I guess the problem is with this function:
function onDocumentMouseDown( event ) {
event.preventDefault();
var vector = new THREE.Vector3( mouse.x, mouse.y, 0.5 );
projector.unprojectVector( vector, camera );
var ray = new THREE.Ray( camera.position, vector.subSelf( camera.position ).normalize() );
var intersects = ray.intersectObjects( objects );
if ( intersects.length > 0 ) {
SELECTED = intersects[ 0 ].object;
var intersects = ray.intersectObject( plane );
offset.copy( intersects[ 0 ].point ).subSelf( plane.position );
}
}
Is anybody have an idea what is the problem?
in this line :
var vector = new THREE.Vector3( mouse.x, mouse.y, 0.5 );
You use the mouse Vector2 object but you don't initialize it.
Something like this should work :
mouse.x = +(event.targetTouches[0].pageX / window.innerwidth) * 2 +-1;
mouse.y = -(event.targetTouches[0].pageY / window.innerHeight) * 2 + 1;