How can i zoom in to a raycaster selection? - three.js

I wonder how can i zoom in to mouse selection with ThreeJs, i'm using raycaster for selecting the model areas but something isn't working when i click on areas - as you can see in My Example (thanks to Mugen87) , my goal is to zoom into faces when i click on them... i've added the following function :
function onClick(event) {
mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;
raycaster.setFromCamera(mouse, camera);
intersects = raycaster.intersectObject( mesh, false, intersects );
if(mesh.type!= 'Mesh' || !intersects.length){
return
}
var n = intersects[ 0 ].face.normal.clone();
n.multiplyScalar(30);
n.add(intersects[ 0 ].point);
var p = intersects[ 0 ].point;
camera.position.copy(n);
camera.lookAt(p);
}
When i click outside the model - it's not keeping the position and it's spinning ...

That's because you're using OrbitControls, which pretty much takes over your camera and assigns its position and rotation once per frame. When you try to set its position manually, OrbitControls overrides anything you did.
The solution is to make your controls variable global. Then when you're ready to take over the camera position, set controls.enabled = false; in the onClick() function. Then when you're ready to return to the regular controls, set controls.enabled = true;
https://threejs.org/docs/#examples/en/controls/OrbitControls.enabled

Related

three js zooming in where the cursor is using orbit controls

I'm very new to three js and is currently trying to implement a feature where the user can zoom in where the cursor is. The plan is to use a raycaster to get the point of intersection and then and use it to update the vector of the orbit controls every time the cursor moves.
the orbit control is initialized like so
this.controls = new OrbitControls( this.camera_, this.threejs_.domElement );
this.controls.listenToKeyEvents( window );
this.controls.screenSpacePanning = false;
this.controls.minDistance = 30;
this.controls.maxDistance = 500;
this.controls.maxPolarAngle = Math.PI / 2;
this is the eventlistener
document.addEventListener('pointermove', (e) => this.onPointerMove(e), false);
and the onPointerMove function looks like this
onPointerMove(event){
const pointer = {
x: (event.clientX / window.innerWidth) * 2 - 1,
y: -(event.clientY / window.innerHeight) * 2 + 1,
}
this.rayCaster.setFromCamera( pointer, this.camera_);
const intersects = this.rayCaster.intersectObjects( this.scene_.children, false);
if ( intersects.length > 0 ) {
this.controls.target(intersects[0].point);
this.controls.update();
}
}
so far, intersects[0].point seems to be getting the intersect coordinate correctly but the orbit control is simply not getting updated. I have also tried changing the camera's position using
this.camera_.position.set(intersects[0].point.x+20,intersects[0].point.y+20,intersects[0].point.z+20);
this.controls.update();
however that just moves my camera everywhere i point.
Edit:
this doesnt work either
const newTarget = new Vector3(intersects[0].point.x,intersects[0].point.y,intersects[0].point.z);
this.controls.target.copy(newTarget);
found the answer here.
Apparently you need to use either copy or set to change the target of the orbit controls. Without calling update().
like so
this.controls.target.set(intersects[0].point.x,intersects[0].point.y,intersects[0].point.z);

Painting the faces of a cube when user clicks on that face

I am trying to do what the title says.
When a user clicks on a face of the cube, that face will change colour.
This is my code snippet:
// create a cube
var cubeGeometry = new THREE.BoxGeometry(20, 20, 20);
var cubeMaterial = new THREE.MeshLambertMaterial({color: 0xffff00 }); //0xF7F7F7 = gray
cube = new THREE.Mesh(cubeGeometry, cubeMaterial);
cube.userData.originalColor = 0xffff00;
function onDocumentMouseClick(event) //if we detect a click event
{
// the following line would stop any other event handler from firing
// (such as the mouse's TrackballControls)
event.preventDefault();
// update the mouse variable
mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;
var vector = new THREE.Vector3( ( event.clientX / window.innerWidth ) * 2 - 1, - ( event.clientY / window.innerHeight ) * 2 + 1, 0.5 );
vector.unproject( camera );
raycaster.set( camera.position, vector.sub( camera.position ).normalize() );
var intersects = raycaster.intersectObject( cube );
if ( intersects.length > 0 )
{
var index = Math.floor( intersects[0].faceIndex / 2 );
switch (index)
{
case 0:
case 1:
case 2:
case 3:
case 4:
case 5:
}
}
}
This code is incomplete.
My problems are these:
I don't know if this strategy is correct and works
I don't know what code to use inside the different cases, in order to paint that side of the cube.
First, consider breaking the cube's faces into draw groups. This answer delves into how to do that.
The idea is that all of the cube's faces will have the same color, but not the same material. This will allow you to change the material's colors individually, and as such you will individually change the color of the cube faces.
When you get a response back from the Raycaster, look for the faceIndex property. This will tell you the index of the face that was intersected. Find which draw group that index belongs to, and you can then reference that group's material index, and thus change the color of the material.

Changing the colour of a face of a cube

I am trying to change the colour of the face of a cube, when the mouse hovers over:
This is what i came up with so far:
function onDocumentMouseMove(event)
{
event.preventDefault();
// update the mouse variable
mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;
var intersects = raycaster.intersectObjects( scene.children );
if ( intersects.length > 0 )
{
var index = Math.floor( intersects[0].faceIndex / 2 );
switch (index)
{
case 0:
case 1:
case 2:
case 3:
case 4:
case 5:
}
}
}
The problem is i dno't know what method to use to paint the faces a different colour inside the switch-case
Take a look at this jsfiddle with code i used from Georges:
https://jsfiddle.net/rand0mus3r/5qe4gyj3/4/
Look only for the onDocumentMouseMouve() function.
I tried your code but it didn't work.
The reason i put your code under the if (cube.material.color == 0xff0000),
was because i still wanted the cube to be painted grey when a hover over.
But when it it selected, thus the colour is red, i wanted on hover over, that side to change.
So when you change the y dimension on the gui and create a cube, and it IS selected (thus the colour is red), when you hover over its sides, it doesn't change colour.
I am stuck as to what to do next, as it is unclear how to paint the sides of the cube.
Here is the code for this function:
function onDocumentMouseMove(event)
{
event.preventDefault();
// update the mouse variable
mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;
// calculate objects intersecting the picking ray
var intersects = raycaster.intersectObjects( scene.children );
if ( intersects.length > 0 && intersects[ 0 ].object === cube && isClicked === false)
{
cube.material.color.set( 0xF7F7F7 );
}
else if (isClicked === false)
{
cube.material.color.set( cube.userData.originalColor );
}
if (cube.material.color == 0xff0000)
{
if ( intersects.length > 0 )
{
// pick the first object. It's the closest one
this.pickedObject = intersectedObjects[0].object;
// save its color
this.pickedObjectSavedColor = this.pickedObject.material.emissive.getHex();
// set its emissive color to flashing red/yellow
this.pickedObject.material.emissive.setHex((time * 8) % 2 > 1 ? 0xFFFF00 : 0xFF0000);
}
}
}
You're on the right track with normalizing your mouse position to pass into the raycaster. This page on ThreeJSFundamentals has a solution that should be similar to you're after, with the exclusion of coloring a specific face. I believe you need to set up your raycaster as
var raycaster = new THREE.Raycaster();
// cast a ray from the camera's location to where the mouse click was registered
raycaster.setFromCamera(mouse, yourCameraName);
Once you've got this, then you can call something similar to the following (taken from the link above)
var intersectedObjects = raycaster.intersectObjects(scene.children);
if (intersectedObjects.length) {
// pick the first object. It's the closest one
this.pickedObject = intersectedObjects[0].object;
// save its color
this.pickedObjectSavedColor = this.pickedObject.material.emissive.getHex();
// set its emissive color to flashing red/yellow
this.pickedObject.material.emissive.setHex((time * 8) % 2 > 1 ? 0xFFFF00 : 0xFF0000);
}
You may also find it's easier to break your cube into an Object3D() of planes, and color the intersected plane

Three.js raycaster intersection with sprites is completely off to the left

I have sprites with text on screen, placed in spherical pattern and I want to allow user to click on individual words and highlight them.
Now the problem is that when I do raycasting and do raycaster.intersectObjects() it returns sprites that are somewhere completely different where the click happened (usually it highlights words that are to the left of the words clicked). For debugging purposes I actually drew the rays from the raycaster object, and they are pretty much going trough the words I clicked.
In this picture I clicked the words "emma", "universe" and "inspector legrasse", but the words that got highlighted are in the red, I also rotated the camera so we can see the lines.
here is the relevant code:
Sprite creation:
var canvas = document.createElement('canvas');
var context = canvas.getContext('2d');
canvas.height = 256;
canvas.width = 1024;
...
context.fillStyle = "rgba(0, 0, 0, 1.0)";
context.fillText(message, borderThickness, fontsize + borderThickness);
var texture = new THREE.Texture(canvas);
texture.needsUpdate = true;
var spriteMaterial = new THREE.SpriteMaterial({map: texture});
var sprite = new THREE.Sprite(spriteMaterial);
sprite.scale.set(400, 100, 1.0);
Individual sprites are then added to "sprites" array, and then added to the scene.
Click detection:
function clickOnSprite(event) {
mouse.x = ( event.clientX / renderer.domElement.clientWidth ) * 2 - 1;
mouse.y = -( event.clientY / renderer.domElement.clientHeight ) * 2 + 1;
raycaster.setFromCamera(mouse, camera);
drawRaycastLine(raycaster);
var intersects = raycaster.intersectObjects(sprites);
if (intersects.length > 0) {
intersects[0].object.material.color.set(0xff0000);
console.log(intersects[0]);
}
}
I am using perspective camera with orbit controls.
I came to the conclusion, that this cannot be currently done with sprites (at least not currently).
As suggested in the comments, I ended up using plane geometries instead of sprites.

Why isn't my raycast intersecting anything?

I have this code, designed to find the mesh the user is clicking on:
// scene and camera are defined outside of this code
var mousePoint = new THREE.Vector2();
var raycaster = new THREE.Raycaster();
var intersections;
function onClick(event) {
mousePoint.x = event.clientX;
mousePoint.y = event.clientY;
raycaster.setFromCamera(mousePoint, camera);
intersections = raycaster.intersectObjects(
scene.children);
}
Yet every time I click, intersections comes back as an empty array, with nothing getting intersected. What am I doing wrong?
From the three.js documentation for Raycaster (emphasis mine):
.setFromCamera ( coords, camera )
coords — 2D coordinates of the mouse, in normalized device coordinates (NDC)---X and Y components should be between -1 and 1.
camera — camera from which the ray should originate
Updates the ray with a new origin and direction.
Therefore, when setting the coordinates of mousePoint, instead of setting x and y directly to event.clientX and event.clientY, they should be converted to this coordinate space:
// calculate mouse position in normalized device coordinates
// (-1 to +1) for both components
mousePoint.x = (event.clientX / window.innerWidth) * 2 - 1;
mousePoint.y = (event.clientY / window.innerHeight) * -2 + 1;

Resources