three js zooming in where the cursor is using orbit controls - three.js

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);

Related

How can i zoom in to a raycaster selection?

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

Does the point coordinate in three.js change if the camera moves?

I'm using the raycaster function to get the coordinates of portions of a texture as a preliminary to creating areas that will link to other portions of my website. The model I'm using is hollow and I'm raycasting to the intersection with the skin of the model from a point on the interior. I've used the standard technique suggested here and elsewhere to determine the coordinates in 3d space from mouse position:
//1. sets the mouse position with a coordinate system where the center
// of the screen is the origin
mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;
console.log("mouse position: (" + mouse.x + ", "+ mouse.y + ")");
//2. set the picking ray from the camera position and mouse coordinates
raycaster.setFromCamera( mouse, camera );
//3. compute intersections
var intersects = raycaster.intersectObjects( scene.children, true );
var intersect = null;
var point = null;
//console.log(intersects);
for ( var i = 0; i < intersects.length; i++ ) {
console.log(intersects[i]);
if (i = intersects.length - 1) {
intersect = intersects[ i ];
point = intersect[ "point" ];
}
This works, but I'm getting inconsistent results if the camera position changes. My assumption right now is that this is because the mouse coordinates are generated from the center of the screen and that center has changed since I've moved the camera position. I know that getWorldPosition should stay consistent regardless of camera movement, but trying to call point.getWorldPosition returns "undefined" as a result. Is my thinking about why my results are inconsistent correct, and if so and I'm right that getWorldPosition is what I'm looking for how do I go about calling it so I can get the proper xyz coordinates for my intersect?
EDITED TO ADD:
When I target what should be the same point (or close to) on the screen I get very different results.
For example, this is my model (and forgive the janky code under the hood -- I'm still working on it):
http://www.minorworksoflydgate.net/Model/three/examples/clopton_chapel_dev.html
Hitting the upper left corner of the first panel of writing on the opposite wall (so the spot marked with the x in the picture) gets these results (you can capture them within that model by hitting C, escaping out of the pointerlock, and viewing in the console) with the camera at 0,0,0:
x: -0.1947601252025508,
​
y: 0.15833788110908806,
​
z: -0.1643094916216681
If I move in the space (so with a camera position of x: -6.140427450769398, y: 1.9021520960972597e-14, z: -0.30737391540643844) I get the following results for that same spot (as shown in the second picture):
x: -6.229400824609087,
​
y: 0.20157559303778091,
​
z: -0.5109691487471469
My understanding is that if these are the world coordinates for the intersect point they should stay relatively similar, but that x coordinate is much different. Which makes sense since that's the axis the camera moves on, but shouldn't it not make a difference for the point of intersection?
My comment will not be related to the camera but I had also an issue about the raycaster and calculating the position of the mouse is more accurate with the following way.
const rect = renderer.domElement.getBoundingClientRect();
mouse.x = ((event.clientX - rect.left) / rect.width) * 2 - 1;
mouse.y = - ((event.clientY - rect.top) / rect.height) * 2 + 1;
So the trick to this when there's no mouse available due to a pointer lock is to use the direction of the ray created by the object controls. It's actually pretty simple, but not really out there.
var ray_direction = new THREE.Vector3();
var ray = new THREE.Raycaster(); // create once and reuse
controls.getDirection( ray_direction );
ray.set( controls.getObject().position, ray_direction );

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

threejs - Defect in rotation - THREE.OrbitControls

I use OrbitControls now but still i have strange bug. It is hard to explain. When i drag mouse down in the begin work normally and then in one moment whole scene begin to rotate in wrong direction and flip my whole scene.
I got warnings :
OrbitControls.js:1103 [Violation] Added non-passive event listener to
a scroll-blocking 'wheel' event. Consider marking event handler as
'passive' to make the page more responsive. See
https://www.chromestatus.com/feature/5745543795965952
Here is my code:
controls = new THREE.OrbitControls(camera, renderer.domElement);
//controls.addEventListener( 'change', render ); // call this only in static scenes (i.e., if there is no animation loop)
controls.enableDamping = true; // an animation loop is required when either damping or auto-rotation are enabled
controls.dampingFactor = 0.05;
controls.screenSpacePanning = true;
controls.minDistance = 14;
controls.maxDistance = 120;
controls.maxPolarAngle = Math.PI / 3;
controls.target.set(5, 4, -20);
I need to limit rotation , disable 360 rotating scene.
For example i wanna allow max angle of 45.
Try this, i had a familiar issue and applied it to my code and worked
camera.up = new THREE.Vector3( 0, 0, 1 );
Did you take a look at the documentation? It outlines four different properties to limit angles of rotation. These are the defaults:
// How far you can orbit vertically, upper and lower limits.
// Range is 0 to Math.PI radians.
controls.minPolarAngle = 0; // radians
controls.maxPolarAngle = Math.PI; // radians
// How far you can orbit horizontally, upper and lower limits.
// If set, must be a sub-interval of the interval [ - Math.PI, Math.PI ].
controls.minAzimuthAngle = - Infinity; // radians
controls.maxAzimuthAngle = Infinity; // radians
Edit:
The above solution is for OrbitControls, which is not what the original question asked. TrackballControls does not offer the ability to limit angles of rotation.

Resources