Rotate Three.js scene on mouse move - three.js

I have a scene redered just fine and need to make it rotate horizontally and/or vertically as the mouse moves over the DOM. I was able to do the horizontal rotation by changind the scene rotation.y property:
const step = 0.005;
let halfWindowWidth = window.innerWidth / 2;
let target = (Math.PI / halfWindowWidth * mouseX / 2) - 0.785398;
if (target < y) {
if (y - (step * 2) > target) {
target = y - (step * 2);
}
} else if (target > y) {
if (y + (step * 2) < target) {
target = y + (step * 2);
}
}
scene.rotation.y = target;
I know that maybe I will need to work on the x and z axis to make it rotate vertically, but I don't know what calculations should I do.
The rotation must take place as the mouse goes further from the center of the DOM, from -90˚ to 90˚ (180˚ in total). The step constant I use to have the rotation be animated and not simply jump when the mouse moves too quick.

Take a look at the following examples
http://threejs.org/examples/#misc_controls_orbit
http://threejs.org/examples/#misc_controls_trackball
there are other examples for different mouse controls, but both of these allow the camera to rotate around a point and zoom in and out with the mouse wheel, the main difference is OrbitControls enforces the camera up direction, and TrackballControls allows the camera to rotate upside-down.
All you have to do is include the controls in your html document
<script src="js/OrbitControls.js"></script>
and include this line in your source
controls = new THREE.OrbitControls( camera, renderer.domElement );

Related

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

three.js raycaster in a container

for my internship in need to make an aplication with three.js what needs to be in a container on a page but it needs an onclick function on the objects. the problem is i cannot find annything on raycasting only in a container and clicking now will not get objects i need
application
onMouseDown(event) {
let s = this;
// calculate mouse position in normalized device coordinates
// (-1 to +1) for both components
s.mouse.x = ( event.clientX / s.renderer.domElement.clientWidth ) * 2 - 1;
s.mouse.y = - ( event.clientY / s.renderer.domElement.clientHeight ) * 2 + 1;
s.intersects = s.raycaster.intersectObjects(s.blocks, true);
for ( var i = 0; i < s.intersects.length;){
s.intersects[ i ].object.material.color.set( 0xff0000 );
console.log(i)
console.log(s.getScene().children)
console.log(s.intersects)
console.log("test 123")
}
if (s.intersects == 0){
console.log(s.mouse.x)
console.log(s.mouse.y)
}
}
edit: it is not the same as Detect clicked object in THREE.js he is not working in a container. plus he has a little problem with the margins for me everywhere i click on the screen it does not detect what i need and i need it to be working only on the container not the whole webpage plus there help is outdated and is not working annymore
If you are working with a canvas that is not at the top-left corner of the page, you need one more step to get to the normalized device coordinates. Note that the NDC in WebGL are relative to the canvas drawing-area, not the screen or document ([-1,-1] and [1,1] are the bottom-left and top-right corners of the canvas).
Ideally, you'd use ev.offsetX/ev.offsetY, but browser-support for that isn't there yet. Instead, you can do it like this:
const {top, left, width, height} = renderer.domElement.getBoundingClientRect();
mouse.x = -1 + 2 * (ev.clientX - left) / width;
mouse.y = 1 - 2 * (ev.clientY - top) / height;
See here for a working example: https://codepen.io/usefulthink/pen/PVjeJr
Another option is to statically compute the offset-position and size of the canvas on the page and compute the final values based on ev.pageX/ev.pageY. This has the benefit of being a bit more stable (as it is not scrolling-dependent) and would allow to cache the top/left/width/height values.

three.js: Limiting camera's rotation

I'm working with three.js, attempting to model a real world camera. As such, I'd like to limit its axis of rotation to 90 degrees along x and y axises.
Is there a simply way to do this? My current code isn't working particularly well (and goes crazy when you attempt to move the camera past the X and Y boundaries simultaneously)
if(xRot != null && xRot != undefined){
camera.rotateX(xRot);
}
if(yRot != null && yRot != undefined){
camera.rotateY(yRot);
}
if(camera.rotation.x < minCameraRotX){
camera.rotation.x = minCameraRotX;
}else if (camera.rotation.x > maxCameraRotX){
camera.rotation.x = maxCameraRotX;
}
if(camera.rotation.y < minCameraRotY){
camera.rotation.y = minCameraRotY;
}else if(camera.rotation.y > maxCameraRotY){
camera.rotation.y = maxCameraRotY;
}
Any advice would be greatly appreciated!!!
I actually managed to find a solution by checking some of the existing code in a Three.js demo for a library called PointerLock. The idea is to actually stack multiple objects inside each other: start with an object that moves horizontally (the yaw object), place another object inside the yaw object that moves vertically (the pitch object), and then place the actual camera inside the pitch object.
Then, you only rotate the outside objects (yaw and pitch) along their respective axises, so if you rotate both, they'll self-correct. For example, if you rotate the yaw 45 degrees along the y-axis (making it turn to the right) and then rotate the pitch 45 degrees (making it turn downward), the pitch will go 45 degrees downward from the yaw's already rotated position.
Given that the camera is inside both, it just points wherever the yaw and pitch direct it.
Here is the code
/*
* CAMERA SETUP
*
* Root object is a Yaw object (which controls horizontal movements)
* Yaw object contains a Pitch object (which controls vertical movement)
* Pitch object contains camera (which allows scene to be viewed)
*
* Entire setup works like an airplane with a
* camera embedded in the propellor...
*
*/
// Yaw Object
var yawObject = new THREE.Object3D();
// Pitch Object
var pitchObject = new THREE.Object3D();
// Camera Object
var camera = new THREE.PerspectiveCamera(fov, aspect, near, far);
// Max Camera angles (in radians)
var minCameraRotX = 0.5;
var maxCameraRotX = 0.5;
var minCameraRotY = 1;
var maxCameraRotY = 1;
// Setup
yawObject.add( pitchObject );
pitchObject.add( camera );
scene.add(yawObject);
...
var rotateCamera = function(xRot, yRot, zRot){
yawObject.rotation.y += yRot;
pitchObject.rotation.x += xRot;
// Enforce X-axis boundaries (rotates around y-axis)
yawObject.rotation.y = Math.max( minCameraRotY, Math.min( maxCameraRotY, yawObject.rotation.y ) );
// Enforce Y-axis boundaries (rotates around x-axis)
pitchObject.rotation.x = Math.max( minCameraRotX, Math.min( maxCameraRotX, pitchObject.rotation.x ) );
}
Here is the source code I referenced: https://github.com/mrdoob/three.js/blob/acda8a7c8f90ce9b71088e903d8dd029e229678e/examples/js/controls/PointerLockControls.js
Also, this is sort of cheesy, but this little plane cartoon helped me visual exactly what was going on in my setup

three.js - focus camera on THREE.Geometry vertices

I want to focus the camera on THREE.Geometry, one vertex at a time and
Transition the camera to the next vertex of the same Geometry
How should i accomplish 1 & 2?
I created a fiddle.
http://jsfiddle.net/5oajajpd/
the function move camera goes through each vertex and sets the camera position. To transition this with animation you can set the x,y, and z properties through the jquery animate function, or your animation lib of choice.
The move camera function is triggered by an interval. In this sphere example it will spiral around and around the sphere forever.
var i = 0;
function moveCamera() {
var point = mesh.geometry.vertices[i];
var coeff = 1 + altitude / rad;
camera.position.x = point.x * coeff;
camera.position.y = point.y * coeff;
camera.position.z = point.z * coeff;
camera.lookAt(mesh.position);
i++;
if (i > mesh.geometry.vertices.length) {
i = 0;
}
}

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