Three.js - Object follows mouse position - javascript

I am creating a sphere in Three.js which has to follow the mouse whenever it moves, as displayed in this example. The function that handles the mouse movement is the following:
function onMouseMove(event) {
// Update the mouse variable
event.preventDefault();
mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
mouse.y = - (event.clientY / window.innerHeight) * 2 + 1;
// Make the sphere follow the mouse
mouseMesh.position.set(event.clientX, event.clientY, 0);
};
I attach a JSFiddle with the complete code inside it, where you can see that according to the DOM, mouseMesh is undefined. Do you have an idea of what am I doing wrong?
Thanks in advance for your replies!

For sphere to follow mouse, you need to convert screen coordinates to threejs world position. Reference link.
Updated fiddle
var vector = new THREE.Vector3(mouse.x, mouse.y, 0.5);
vector.unproject( camera );
var dir = vector.sub( camera.position ).normalize();
var distance = - camera.position.z / dir.z;
var pos = camera.position.clone().add( dir.multiplyScalar( distance ) );

You should use a THREE.Raycaster for this. When you set a list of intersectObjects you will be able to get an array of objects that intersected with the ray. So you can get the position from the 'clicked' object from returned list
Basically, you need to project from the 3D world space and the 2D screen space.
Renderers use projectVector for translating 3D points to the 2D screen. unprojectVector is basically for doing the inverse, unprojecting 2D points into the 3D world. For both methods you pass the camera you're viewing the scene through.
So, in this code you're creating a normalised vector in 2D space.

Related

How to get an object's world position (Three.js)

I want to place my already placed object at a new location, but it moves from the local position and not global.
this._scene.updateMatrixWorld();
this._scene.add(mesh);
var v1 = new THREE.Vector3();
v1.setFromMatrixPosition( mesh.matrixWorld );
mesh.position.set(v1.x +2, v1.y + 2, v1.z + 2);
What I did to solve my problem:
Every mesh got a geometry attribute. That is why you can call mesh.geometry.
From this point i created a BoundingSphere around my mesh.
mesh.geometry.computeBoundingSphere();
Now it is possible to get the world position of my boundingSphere, which simultaneously is the world position of my mesh.
var vector = mesh.geometry.boundingSphere.center;
Congratulations! 'vector' got the center world position in (x,y,z) of your mesh.
Just to clarify, 'mesh' is a THREE.Mesh object.
Wouldn't you just then move the object itself by the increment? I'm not sure why you need the matrix involved?
mesh.position.set(mesh.position.x +2, mesh.position.y + 2, mesh.position.z + 2);
edit... if you need to use matrix, you need to set the matrixWorld. Look at http://threejs.org/docs/#Reference/Core/Object3D.matrixWorld but using the position setter will do the heavy lifting for you.

Three.js / WebGL - Sphere - scene visible countries / colors

I had implemented a globe similar to this one
But while trying to fetch the countries visible to the scene in Three.js when the globe is in a stationary position am stuck in a issue. I can retrieve the country color code when I mouse over / click on a specific country using readPixels and get the appropriate color code of the pixel as shown below
var gl = renderer.context;
var mx = ( mouseX + renderer.context.canvas.width/2 );//(mouseX + renderer.context.canvas.width/2) * 0.25;
var my = ( -mouseY + renderer.context.canvas.height/2 );//(-mouseY + renderer.context.canvas.height/2) * 0.25;
mx = Math.floor( mx );
my = Math.floor( my );
var buf = new Uint8Array( 4 );
gl.readPixels( mx, my, 1, 1, gl.RGBA, gl.UNSIGNED_BYTE, buf );
But when the globe stopped rotating then I need to get the list of user viewable countries from the scene, I tried to use Raycaster to get the mesh and scan through all the pixels and get the country code but seems to be a performance hit for us
Appreciate if someone can provide their views
You can render the scene to a texture (even with different shaders/uniforms). There are examples on the official site, like this: http://threejs.org/examples/webgl_rtt.html
For instance you could have a scene where the texture of the globe is country coded, and when the user clicks, you render that scene into a texture and read single pixel from that texture.
Be careful, when rendering to textures, you sometimes have to force update your textures by setting needsUpdate to true.
Hope I helped.

Three.js raycasting object selection with animation

Raycasting selection is working fine for my project on static meshes, however for animated meshes the ray selection doesn't seem to see the movement of the mesh and only responds to the mesh's non-animated (original) position.
My selection code is as follows:
element.addEventListener( 'mouseup', function ( event )
{
var vector = new THREE.Vector3(( event.clientX / window.innerWidth ) * 2 - 1, -( event.clientY / window.innerHeight ) * 2 + 1, 0.5);
vector = vector.unproject(camera);
var raycaster = new THREE.Raycaster(camera.position, vector.sub(camera.position).normalize());
var intersects = raycaster.intersectObjects(pickable_objects, true);
if (intersects.length > 0)
{
//I change the object's material colour so that I can see the selection
}
}
Where pickable_objects is an array of three.js mesh objects that are selectable, these do not carry animation information as far as I am aware, so the result I'm complaining about may not be surprising to some.
I haven't included the colour changing code or the JSON mesh reader code here as I don't believe it's pertinent.
So with static meshes the ray casting is very accurate, but for animated meshes I have to click in the very centre of the object or find its original non-animated position to make the selection work.
If you take a look at the source code in the github repo, particularly Raycaster.js, you'll see that the actual implementation is in the geometry object. For instance Mesh.js implements the raycast by iterating over its triangles and checking whether the ray intersects any of them but SkinnedMesh.js does not override this method.
Finally, a quick search in the issues page showed that this is actually a known limitation - https://github.com/mrdoob/three.js/issues/6440.
You seem to be left with 2 options - bump the issue in github and ask for an ETA or implement this yourself (and send a pull request afterwards hopefully).

Getting the real screen width in pixels of a div/image using Three.js and the CSS3DRenderer

Really been struggling with this one.
I have a program that uses the CSS3D Renderer of Three.js and I am trying to get projected "real" screen-width in pixels of a div (which contains a single image).
I've tried using getBoundingClientRect() on the image but the widths and heights returned from that function are wrong. I assume due to a perspective issue.
I have also tried this method which gives me an accurate center coordinate and is from this post by mrdoob: https://stackoverflow.com/a/11605007
var width = window.innerWidth, height = window.innerHeight;
var widthHalf = width / 2, heightHalf = height / 2;
var projector = new THREE.Projector();
var vector = projector.projectVector( object.matrixWorld.getPosition().clone(), this.camera );
vector.x = ( vector.x * widthHalf ) + widthHalf;
vector.y = -( vector.y * heightHalf ) + heightHalf;
Unfortunately all methods I am finding for getting the top,left corner or any corner of the object use Geometry objects which the CSS3DObject does not use (ie. getting the vector of the corner vertices and projecting those vectors ref: https://stackoverflow.com/a/14044103/2009076).
Anyone have any idea of how to accomplish this? To be concise, I would just like to get the real on-screen size of a div (which fits tightly around its img) in a Three.js CSS3D scene. In the end I would like to detect if the camera zoom has made an image bigger than its original size in pixels.
Thanks!

Three.js: Camera flying around sphere?

In Three.js (which uses JavaScript/ WebGL), how would one create a camera which flies around a sphere at fixed height, fixed forward speed, and fixed orientation in relation to the sphere, with the user only being able to steer left and right?
Imagine an airplane on an invisible string to the center of a globe, flying near ground and always seeing part of the sphere:
(I currently have code which rotates the sphere so to the camera it looks like it's flying -- left and right steering not implemented yet -- but I figure before I go further it might be cleaner to move the camera/ airplane, not the sphere group.)
Thanks!
You mean like in my Ludum Dare 23 game? I found this to be a bit more complicated than I expected. It's not difficult, though.
Here I'm assuming that you know the latitude and longitude of the camera and its distance from the center of the sphere (called radius), and want to create a transformation matrix for the camera.
Create the following objects only once to avoid creating new objects in the game loop:
var rotationY = new Matrix4();
var rotationX = new Matrix4();
var translation = new Matrix4();
var matrix = new Matrix4();
Then every time the camera moves, create the matrix as follows:
rotationY.setRotationY(longitude);
rotationX.setRotationX(-latitude);
translation.setTranslation(0, 0, radius);
matrix.multiply(rotationY, rotationX).multiplySelf(translation);
After this just set the camera matrix (assuming camera is your camera object):
// Clear the camera matrix.
// Strangely, Object3D doesn't have a way to just SET the matrix(?)
camera.matrix.identity();
camera.applyMatrix(matrix);
Thanks for Martin's answer! I've now got it running fine in another approach as follows (Martin's approach may be perfect too; also many thanks to Lmg!):
Set the camera to be a straight line atop the sphere in the beginning (i.e. a high y value, a bit beyond the radius, which was 200 in my case); make it look a bit lower:
camera.position.set(0, 210, 0);
camera.lookAt( new THREE.Vector3(0, 190, -50) );
Create an empty group (an Object3D) and put the camera in:
camGroup = new THREE.Object3D();
camGroup.add(camera);
scene.add(camGroup);
Track the mouse position in percent in relation to the screen half:
var halfWidth = window.innerWidth / 2, halfHeight = window.innerHeight / 2;
app.mouseX = event.pageX - halfWidth;
app.mouseY = event.pageY - halfHeight;
app.mouseXPercent = Math.ceil( (app.mouseX / halfWidth) * 100 );
app.mouseYPercent = Math.ceil( (app.mouseY / halfHeight) * 100 );
In the animation loop, apply this percent to a rotation, while automoving forward:
camGroup.matrix.rotateY(-app.mouseXPercent * .00025);
camGroup.matrix.rotateX(-.0025);
camGroup.rotation.getRotationFromMatrix(camGroup.matrix);
requestAnimationFrame(animate);
renderer.render(scene, camera);

Categories