Three.js raycaster to intersect only specified part of shape - javascript

I need to get an intersection using raycaster only with vertices of a geometry and not with all the geometry shape. I wrote this code but nothing happen if I click on vertice setted.
function onDocumentMouseClick(event) {
event.preventDefault();
mouse2D.x = (event.clientX / window.innerWidth) * 2 - 1;
mouse2D.y = -(event.clientY / window.innerHeight) * 2 + 1;
raycaster = projector.pickingRay(mouse2D.clone(), camera);
var intersects = raycaster.intersectObjects(objects[0].geometry.vertices[0]);// I want intersection only with vertices
if (intersects.length > 0) {
console.log("ok");}

First, you need to pass an array of objects to raycaster.intersectObjects, as suggested by pixelmike:
var intersects = raycaster.intersectObjects([objects[0]])
You can't get the vertex of the intersection per se, but you can get the face for each intersection:
for ( var i = 0; i < intersects.length; i++ ) {
if ( intersect.face == myTargetFace ) {
console.log( "ok" );
}
}
If you really want the particular object to only check for intersections with a particular subset of faces, you could override the raycast method of that object (a la https://github.com/mrdoob/three.js/blob/master/src/objects/Mesh.js#L56-L340 ) to only search those faces.
three.js r68

Related

Raycast not detecting GlTF file/ Three.js

My binary gltf file(modelled in blender and animated using mixamo) is not detecting on raycast.
I read bunch of tutorials and questions about it to try to fix it but it does not work what so ever:(
const loader = new GLTFLoader();
let modelLoader = "/models/c2.glb";
let model, mixer, neck, waist;
loader.load(modelLoader, (gltf) => {
model = gltf.scene;
let fileAnimations = gltf.animations;
model.traverse((o) => {
// console.log(o);
if (o.isBone && o.name === "mixamorigNeck") {
neck = o;
}
if (o.isBone && o.name === "mixamorigSpine") {
waist = o;
}
if (o.isMesh) {
o.material.reflectivity = 1;
}
});
document.addEventListener("click", (e) => raycast(e));
document.addEventListener("touchend", (e) => raycast(e, true));
function raycast(e, touch = false) {
const raycaster = new THREE.Raycaster();
const mouse = new THREE.Vector2();
e.preventDefault();
if (touch) {
mouse.x = 2 * (e.changedTouches[0].clientX / window.innerWidth) - 1;
mouse.y = 1 - 2 * (e.changedTouches[0].clientY / window.innerHeight);
} else {
mouse.x = (e.clientX / window.innerWidth) * 2 - 1;
mouse.y = -(e.clientY / window.innerHeight) * 2 + 1;
}
raycaster.setFromCamera(mouse, camera);
const intersects = raycaster.intersectObjects(scene.children, true);
console.log(scene.children);
if (intersects.length > 0) {
console.log("hi");}}
It does not have error, and if I console.log scene.children outside of ‘if (intersects){}’ and click on the window, it works and contains everything it should, but if I put it inside the ‘if’ and click on the 3d object it is empty array.
I also tried to .push the meshes inside new array and detect but did not work.
Please help!
The problem is that you are performing ray casting against a skinned mesh. And three.js is currently (r128) not able to compute proper bounding volumes for this type of 3D object. Bounding volumes however are important for ray casting since they are used to detect early outs.
The workaround for this issue is to manually define bounding volumes so they properly enclose the skinned mesh. I suggest you traverse through gltf.scene and set the boundingSphere and boundingBox property of the skinned mesh's geometry.
More information at GitHub here: https://github.com/mrdoob/three.js/pull/19178

can three.js raycaster interset with the Group model exported by fbx2gltf?

Help bro. I use three.js raycaster() function to intersect a Object3D in the Group, but it return nothing....and the raycaster() can intersect with the THREE.mesh if I put a mesh into the scene, SO Does it means that the raycaster() function can't support the import Object?
code to intersect on the click event
function onMouseClick(event) {
mouse.x = ((event.clientX - canvas.getBoundingClientRect().left) / canvas.offsetWidth) * 2 - 1;
mouse.y = - ((event.clientY - canvas.getBoundingClientRect().top) / canvas.offsetHeight) * 2 + 1;
raycaster.setFromCamera(mouse, camera);
var clickobjstore = raycaster.intersectObjects(huojia.children[0], true);
console.log(clickobjstore);
for (var i = 0; i < clickobjstore.length; i++) {
console.log(clickobjstore);
clickobjstore[i].object.material.color.set(0xff0000);
}
renderer.render(scene, camera);
}
img of the group object on console
the group object on the memory

Picking objects with raycasters

Trying to pick some objects on click, and I'm trying to use the standard code used in every example:
function onMouseDown(evt) {
evt.preventDefault();
canvasAbsoluteHeight = $('canvas').height();
canvasAbsoluteWidth = $('canvas').width();
mouseDown = true;
mouseX = evt.offsetX == undefined ? evt.layerX : evt.offsetX;
mouseY = evt.offsetY == undefined ? evt.layerY : evt.offsetY;
var mouse = new THREE.Vector3();
mouse.x = ( evt.clientX / canvasAbsoluteWidth ) * 2 - 1;
mouse.y = 1 - ( evt.clientY / canvasAbsoluteHeight ) * 2;
mouse.z = 0;
ray = new THREE.Raycaster( mouse, camera );
var intersects = ray.intersectObjects( objects);
console.log('intersects', intersects);
if ( intersects.length > 0 ) {
console.log('intersects', intersects);
}
}
objects is an array of THREE.Object3D which should be able to be picked.
I think it may be something connected with the camera. My camera is a child of THREE.Object3D for easier manipulation, and the parent object is not set at origin.
Other thing is that canvas is not fullscreen, which may have something with mouse position? (it is inside a div with some offset from the edges of the page).
Check this fiddle. There is Picker class that you can use. First of all you have to init this class with camera and domelement
Picker.init(camera,domelement)
and then you have to attach mesh
Picker.attach(mesh)
and after that you have to specify what do you want to do after mosedown/mouseup in Picker methods.

Three.js: "select tool" How to detect the intersection of a 2D square and 3D objects

Basically what i want to create:
I have a 3D map with objects, i want to select all objects that are in the 2D box x1,y1 to x2,y2 on my screen.
Any ideas how this has to be done, because i'm clueless on how to start.
Thanks in advance!
prevX and prevY is coordinate of mouse down:
function onDocumentMouseUp(event) {
event.preventDefault();
var x = (event.clientX / window.innerWidth) * 2 - 1;
var y = -(event.clientY / window.innerHeight) * 2 + 1;
var width = (x - prevX); //* window.innerWidth;
var height = (y - prevY); //* window.innerHeight;
var dx = prevX; //* window.innerWidth;
var dy = prevY; //* window.innerHeight;
console.log(
dx + ',' +
dy + "," +
(dx + width) + "," +
(dy + height) +
", width=" + width +
", height=" + height
);
var topLeftCorner3D = new THREE.Vector3(dx, dy, 1).unproject(
camera);
var topRightCorner3D = new THREE.Vector3(dx + width, dy, 1)
.unproject(camera);
var bottomLeftCorner3D = new THREE.Vector3(dx, dy + height,
1).unproject(camera);
var bottomRightCorner3D = new THREE.Vector3(dx + width, dy +
height, 1).unproject(camera);
var topPlane = new THREE.Plane();
var rightPlane = new THREE.Plane();
var bottomPlane = new THREE.Plane();
var leftPlane = new THREE.Plane();
topPlane.setFromCoplanarPoints(camera.position,
topLeftCorner3D, topRightCorner3D);
rightPlane.setFromCoplanarPoints(camera.position,
topRightCorner3D, bottomRightCorner3D);
bottomPlane.setFromCoplanarPoints(camera.position,
bottomRightCorner3D, bottomLeftCorner3D);
leftPlane.setFromCoplanarPoints(camera.position,
bottomLeftCorner3D, topLeftCorner3D);
//var frustum = new THREE.Frustum( topPlane, bottomPlane, leftPlane, rightPlane, nearPlane, farPlane);
function isObjectInFrustum(object3D) {
var sphere = object3D.geometry.boundingSphere;
var center = sphere.center;
var negRadius = -sphere.radius;
if (topPlane.distanceToPoint(center) < negRadius) { return false; }
if (bottomPlane.distanceToPoint(center) < negRadius) { return false; }
if (rightPlane.distanceToPoint(center) < negRadius) { return false; }
if (leftPlane.distanceToPoint(center) < negRadius) { return false; }
return true;
}
var matches = [];
for (var i = 0; i < window.objects.length; i++) {
if (isObjectInFrustum(window.objects[i])) {
window.objects[i].material = window.selectedMaterial;
}
}
}
Intersecting a box in the screen space is equivalent to intersecting a pyramid (perspective) or a cube (orthogonal view) the 3D space. I think you should define a THREE.Frustum based on your 2D box.
For perspective camera:
convert the screen-space box corner coordinates to 3D vectors (a vector from the camera position to the given point)
var topLeftCorner3D = new THREE.Vector3( topLeftCorner2D.x, topLeftCorner2D.y, 1 ).unproject(
camera );
construct 4 planes based on these points (one plane for one box side). The third point of the plane is the camera position.
topPlane.setFromCoplanarPoints (camera.position, topLeftCorner3D , topRightCorner3D )
rightPlane.setFromCoplanarPoints (camera.position, topRightCorner3D , bottomRightCorner3D )
bottomPlane.setFromCoplanarPoints (camera.position,bottomRightCorner3D , bottomLeftCorner3D )
leftPlane.setFromCoplanarPoints (camera.position, bottomLeftCorner3D , topLeftCorner3D )
Construct a Frustum based ot these planes, adding the camera near and far planes to them.
var frustum = new THREE.Frustum( topPlane, bottomPlane, leftPlane, rightPlane, nearPlane, farPlane);
Intersect the frustum with the objects
frustum.intersectsBox(object.geometry.boundingBox)
or
frustum.intersectsSphere(object.geometry.boundingSphere)
An alternative to using the Frustum object itself: you can skip the near and far planes, you only have to check whether the object is in the space bordered with your 4 planes. You can write a function like Frustum.intersectSphere() method with only 4 planes:
function isObjectInFrustum(object3D) {
var sphere = object3D.geometry.boundingSphere;
var center = sphere.center;
var negRadius = - sphere.radius;
if ( topPlane.distanceToPoint( center )< negRadius ) return false;
if ( bottomPlane.distanceToPoint( center )< negRadius ) return false;
if ( rightPlane.distanceToPoint( center )< negRadius ) return false;
if ( leftPlane.distanceToPoint( center )< negRadius ) return false;
return true;
}
You can use the bounding boxes of the objects. THREE.Geometry contains a boundingBox property. This is null by default, you should compute it by calling computeBoundingBox() directly.
scene.traverse( function ( node ) {
if ( node.geometry )
node.geometry.computeBoundingBox();
} );
Once you have the bounding boxes, the bounding box 2D coordinates are (if 'y' is the UP axis):
mesh.geometry.boundingBox.min.x
mesh.geometry.boundingBox.min.z
mesh.geometry.boundingBox.max.x
mesh.geometry.boundingBox.max.z
See http://threejs.org/docs/#Reference/Core/Geometry
(however, if you need the exact result for meshes that have non-rectangular shape, you need further computations)
For a more accurate method than subfrustum selection see marquee selection with three js. It works by projecting the 8 corners of bounding boxes onto screen space and then intersecting with screen rectangle. See also here for a 3rd faster method (written in ClojureScript) that projects 3D bounding sphere onto bounding circle in screen space, as well as implementations of the first two methods. See also related stack overflow question that tries to use GPU to perform screen projection and picking with readpixels (I haven't looked into this method since I assume readpixels would stall the rendering pipeline too much, but let me know if I am wrong).
I made a working example of subfrustum selection at http://jsbin.com/tamoce/3/ , but it is too inaccurate. The important part is:
var rx1 = ( x1 / window.innerWidth ) * 2 - 1;
var rx2 = ( x2 / window.innerWidth ) * 2 - 1;
var ry1 = -( y1 / window.innerHeight ) * 2 + 1;
var ry2 = -( y2 / window.innerHeight ) * 2 + 1;
var projectionMatrix = new THREE.Matrix4();
projectionMatrix.makeFrustum( rx1, rx2, ry1, ry2, camera.near, camera.far );
camera.updateMatrixWorld();
camera.matrixWorldInverse.getInverse( camera.matrixWorld );
var viewProjectionMatrix = new THREE.Matrix4();
viewProjectionMatrix.multiplyMatrices( projectionMatrix, camera.matrixWorldInverse );
var frustum = new THREE.Frustum();
frustum.setFromMatrix( viewProjectionMatrix );

Detect clicked object in THREE.js

I have a THREE.js scene where a lot of elements appear, and I need to detect what object the user is clicking on.
What I have done so far is the following. The camera does not move to much - it only changes the vertical position by a limited amount, always looking towards the same point. My approximate method is the following:
I take the coordinates if the click relative to the canvas
I translate them into horizontal and vertical coordinates in the webGL scene by means of a simple rescaling, and add a Z coordinate which is sufficiently far away.
I take a horizontal ray starting from the point above, constructed by THREE.Ray()
I use ray.intersectObjects() to find the first element along the ray.
This method approximately works, but it is sometimes a few pixels away from the actual point.
Is there a more reliable technique to find out the object where a user has clicked?
Depends on what kind of camera are you using.
1) PerspectiveCamera: is ok link that Mr.doob provides.
2) OrthographicCamera: is quite different:
var init = function() {
camera = new THREE.OrthographicCamera( SCREEN_WIDTH / - 2, SCREEN_WIDTH / 2, SCREEN_HEIGHT / 2, SCREEN_HEIGHT / - 2, NEAR, FAR);
document.addEventListener( 'mousedown', onDocumentMouseDown, false );
}
function onDocumentMouseDown( e ) {
e.preventDefault();
var mouseVector = new THREE.Vector3();
mouseVector.x = 2 * (e.clientX / SCREEN_WIDTH) - 1;
mouseVector.y = 1 - 2 * ( e.clientY / SCREEN_HEIGHT );
var raycaster = projector.pickingRay( mouseVector.clone(), camera );
var intersects = raycaster.intersectObject( TARGET );
for( var i = 0; i < intersects.length; i++ ) {
var intersection = intersects[ i ],
obj = intersection.object;
console.log("Intersected object", obj);
}
}
Check out this one:
var camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 1, 5000);
var object; //your object
document.addEventListener('mousedown', onMouseDown, false);
function onMouseDown(e) {
var vectorMouse = new THREE.Vector3( //vector from camera to mouse
-(window.innerWidth/2-e.clientX)*2/window.innerWidth,
(window.innerHeight/2-e.clientY)*2/window.innerHeight,
-1/Math.tan(22.5*Math.PI/180)); //22.5 is half of camera frustum angle 45 degree
vectorMouse.applyQuaternion(camera.quaternion);
vectorMouse.normalize();
var vectorObject = new THREE.Vector3(); //vector from camera to object
vectorObject.set(object.x - camera.position.x,
object.y - camera.position.y,
object.z - camera.position.z);
vectorObject.normalize();
if (vectorMouse.angleTo(vectorObject)*180/Math.PI < 1) {
//mouse's position is near object's position
}
}
Checks for intersection of the mouse and any of the Cubes in 3d space and alters it's color. Maybe this help you.
I ran into problems trying to implement this for a canvas which does not take up the entire width and height of the screen. Here is the solution I found works quite well.
Initialize everything on an existing canvas:
var init = function() {
var canvas_model = document.getElementById('model')
var viewSize = 50 // Depending on object size, canvas size etc.
var camera = new THREE.OrthographicCamera(-canvas_model.clientWidth/viewSize, canvas_model.clientWidth/viewSize, canvas_model.clientHeight/viewSize, -canvas_model.clientHeight/viewSize, 0.01, 2000),
}
Add an event listener to the canvas:
canvas_model.addEventListener('click', function(event){
var bounds = canvas_model.getBoundingClientRect()
mouse.x = ( (event.clientX - bounds.left) / canvas_model.clientWidth ) * 2 - 1;
mouse.y = - ( (event.clientY - bounds.top) / canvas_model.clientHeight ) * 2 + 1;
raycaster.setFromCamera( mouse, camera );
var intersects = raycaster.intersectObjects(scene.children, true);
if (intersects.length > 0) {
// Do stuff
}
}, false)
Or for a 'touchstart' event, change the lines calculating the mouse.x and mouse.y into:
mouse.x = ( (event.touches[0].clientX - bounds.left) / canvas_model.clientWidth ) * 2 - 1;
mouse.y = - ( (event.touches[0].clientY - bounds.top) / canvas_model.clientHeight ) * 2 + 1;

Categories

Resources