How can I click on coordinates on globe? - three.js

I am using chrome experiment's globe http://globe.chromeexperiments.com and I would like to click on any color coordinates to show popup but event listener is not being called. can somebody assist me how to do it?

You could try casting a ray from the camera into the scenery and then getting the intercepts. See the docs for rayCaster.
What you would do is create a new rayCaster object, then get the mouse coordinates of the user's screen. You would need to create some kind of "onclick" event on the parent element of the canvas.
Once the event occurs, the rayCaster object has a property which handles our situation, "setFromCamera". However, you need to provide it a list of all of the objects that you are interested in intersecting, that is all of the rectangles.
So you code would look something like this, note that I used jQuery for this:
var rayCaster = new THREE.Raycaster();
var onClickEventHandler = function(e) {
//Save the mouse data as a vector
//width and height pertain to the size of the canvas.
var mousePosition = new THREE.Vector3((e.offsetX / width) * 2 - 1,
-(e.offsetY / height) * 2 + 1,
0.5);
rayCaster.setFromCamera(mousePosition, camera);
//"objects" is the array of meshes that you care about the user intersecting
var intersects = rayCaster.intersectObjects(objects);
}
What is contained inside the "intersects" array is all of the items from the "objects" array which ere intersected by the ray in the order of intersection. You are likely interested in the first element of this array.
I don't know if that globe utility provides a better way of doing it, what I showed is the generic way of selecting an object in the scenery.

Related

Make children of imported Three.js group follow pointer when their canvas gets hovered

In Three.js, I have a group of meshes that is loaded from outside with help of FBX loader. The group has six meshes inside. My task is to make this meshes follow pointer when they get hovered. More precisely, I'd like to have a sort of magnetic effect (just like navbar items in this pen, but with meshes in Three.js).
I think, firstly, I have to detect, where currently pointer is, i.e. get position of cursor in world coordinates system, and then translate meshes towards it. But when I try to get the position of cursor, it seems to be wrong.
Having said that, I have two questions:
How to get proper cursor's position relative to the world coordinates?
How to change position of each of the group's meshes so that they get translated against the cursor?
Here is what have I done so far:
Hi everyone.
In Three.js, I have a group of meshes that is loaded from outside with help of FBX loader. The group has six meshes inside. My task is to make this meshes follow pointer when their canvas get hovered. More precisely, I'd like to have a sort of magnetic effect (just like navbar items in this pen, but with meshes of Three.js).
I think, firstly, I have to detect, where currently pointer on canvas is, i.e. get position of cursor in world coordinates system, and then translate meshes towards it. But when I try to get the position of cursor, it seems to be wrong.
Having said that, I have two questions:
How to get proper cursor's position relative to the world coordinates?
How to change position of each of the group's meshes so that they get translated against the cursor?
Here is what have I done so far. Function that translates meshes isn't written yet. Mousemove callback returns pretty big digits, though:
// Load object and play a third-party animation
loader.load("Object_001.fbx", (object) => {
mixer = new THREE.AnimationMixer(object);
const action = mixer.clipAction(object.animations[0]);
action.play();
object.traverse((child) => {
if (child.isMesh) {
child.material.map = texture;
child.material.needsUpdate = true;
}
});
scene.add(object);
});
// log coordinates of the pointer
const mouse = new THREE.Vector3();
const position = new THREE.Vector3();
function onMouseMove(event) {
mouse.set(
(event.clientX / window.innerWidth) * 2 - 1,
-(event.clientY / window.innerHeight) * 2 + 1,
0.5
);
mouse.unproject(camera);
mouse.sub(camera.position).normalize();
const distance = -camera.position.z / mouse.z;
position.copy(camera.position).add(mouse.multiplyScalar(distance));
console.log(position);
}
wrapperElement.addEventListener("mousemove", onMouseMove);
Thanks in advance.
Made a codepen here:
https://codepen.io/cdeep/pen/YzxPPZQ
The cursor only exists in the canvas dom element which is a rendering of the camera view frustum.
The easiest way to make the object follow a mouse is to get the point of intersection of the mouse with another object in the 3d scene and set the object position to the intersection point. The above codepen showcases that.
raycaster.setFromCamera( mouse, camera );
const intersects = raycaster.intersectObjects([ground]);
if(intersects.length) {
const { point } = intersects[0];
cube.position.copy(point.setY(0.5));
}
You could also position it at a fixed distance from the mouse but it looks odd in my opinion:
const distance = 10;
raycaster.setFromCamera( mouse, camera );
const { origin, direction } = raycaster.ray;
cube.position.copy(origin.clone().add(direction.multiplyScalar(distance)));
Documentation for raycaster:
https://threejs.org/docs/index.html?q=ray#api/en/core/Raycaster
Raycasting is used for mouse picking (working out what objects in the
3d space the mouse is over) amongst other things.

threejs - raycasting in AR with controller after repositioning

I'm rather new to threejs, so what I'm doing might not be the most efficient way.
I have an object in AR on a mobile device and I want to know if I intersect with it when touching on the screen.
I use the following code to generate the raycast, and it works initally.
const tempMatrix = new THREE.Matrix4();
tempMatrix.identity().extractRotation(this.controller.matrixWorld);
this.raycaster.ray.origin.setFromMatrixPosition(this.controller.matrixWorld);
this.raycaster.ray.direction.set(0, 0, -1).applyMatrix4(tempMatrix);
However, I have the ability to reposition the object (i.e. reset the position so the object is in front, relative to the current camera direction and position) by moving and rotating the whole scene.
After the repositioning, the raycasting is completely offset and is not casting rays anywhere near where I touch the screen.
Repositioning is done like this (while it works, if there's a better way, let me know!) :
public handleReposition(): void {
const xRotation = Math.abs(this.camera.rotation.x) > Math.PI / 2 ? -Math.PI : 0;
const yRotation = this.camera.rotation.y;
this.scene.rotation.set(xRotation, yRotation, xRotation);
this.scene.position.set(this.camera.position.x, this.camera.position.y, this.camera.position.z);
}
How can I achieve to raycast to the correct new location?
Thanks!
Assuming this.scene is actually the main threejs Scene, it's usually a bad idea to change its rotation or position, since it will affect everything inside the scene, including the controller. I'd suggest moving your object instead, or add your object(s) to a Group and move that.

ThreeJS Raycaster doesn't find intersected objects if canvas isn't full screen

I created a scene with multiple objects in the canvas element with ThreeJS. I now want to change the material color of an object if the user clicks on it.
Excerpt of my Angular 5 component, which holds all the ThreeJS content:
public onMouseDown(event: MouseEvent) {
console.log("onMouseDown");
event.preventDefault();
var rayCaster = new THREE.Raycaster();
var mouse = new THREE.Vector2();
mouse.x = (event.clientX / this.renderer.domElement.clientWidth) * 2 - 1;
mouse.y = -(event.clientY / this.renderer.domElement.clientHeight) * 2 + 1;
rayCaster.setFromCamera(mouse, this.camera);
var intersects = rayCaster.intersectObjects(this.scene.children);
console.log("Scene has " + obj.length + " objects");
console.log(intersects.length + " intersected objects found");
intersects.forEach((i) => {
i.object.material =
new THREE.MeshBasicMaterial({color: 0xf1f11f});
});
this.render();
}
The problem is that the mouse coordinates are not correct, due to the distance between canvas element and the document outer edge. Whenever I click in the scene, an object right of the mouse will get the new material color.
Some information:
this.renderer.domElement.clientWidth holds the width of the canvas element
How to calculate the correct mouse coordinates if the canvas element isn't full screen?
If your flow is broken (your container is floated or manually positioned), then clientX/clientY will report "bad" values (relative to the page, rather than to the container like you might expect). The properties offsetX/offsetY account for this by reporting values relative to the container on which the event was fired.
According to MDN, support is limited, but my testing just now (01/22/2018) showed the latest versions of Chrome, Firefox, Edge, and IE all reported the correct values*. Just note that some browsers report integer values, while others may report double values.
*This may not hold true for Safari or mobile browsers. YMMV.

Get mouse clicked point's 3D coordinate in three.js

I'm new in THREE.js.
I'm trying to get 3D coordinates of point on mouse click on the object (not simple objects: Box, Sphere,..) in Canvas.
In detail, I'm working with 3D objects viewer - I have camera (THREE.PerspectiveCamera), mouse controls (rotate, zoom, move), add/remove objects (my own object, loaded using loaders for THREE.js) in scene,.. And I want to add a function, which gets 3D coordinates for clicked point in 3D.
Exactly, I want coordinates of the end point of a ray - begining from mouse click on the camera_near_window and ending to the object's point, I've clicked on..
I tried a lot of ways to do it:
Getting coordinates of point on z=0 plane -- It works fine, but it is on z=0 plane and it is not that I need, cause I have OrbitControls..
THREE.js example - clickable objects -- It uses CanvasRenderer (not WebGLRenderer) and works for a little objects (but works for my project): browser crashes when I load many objects (CanvasRenderer needs 5x more memory then WebGLRenderer).
"How to get object in WebGL 3d space from a mouse click coordinate" - I tried this one too, but raycaster.intersectObjects found nothing, intersects was an empty array (maybe it works for only simple objects like box, sphere,..).
Can anyone show me the demo code which gets 3D point coords for clicked point of clicking object in 3D, please..?
So, as I think this question is useful for someone, I'll answer it myself (I'll write my resolve):
var renderer, canvas, canvasPosition, camera, scene, rayCaster, mousePosition;
function init() {
renderer = new THREE.WebGLRenderer({ antialias: false });
canvas = renderer.domElement;
canvasPosition = $(canvas).position();
camera = new THREE.PerspectiveCamera(20, $(canvas).width() / $(canvas).height(), 0.01, 1e10);
scene = new THREE.Scene();
rayCaster = new THREE.Raycaster();
mousePosition = new THREE.Vector2();
scene.add(camera);
var myObjects = new THREE.Object3D();
// myObjects.add( your object );
// myObjects.add( your object );
// myObjects.add( your object );
myObjects.name = 'MyObj_s';
scene.add(myObjects);
};
function getClicked3DPoint(evt) {
evt.preventDefault();
mousePosition.x = ((evt.clientX - canvasPosition.left) / canvas.width) * 2 - 1;
mousePosition.y = -((evt.clientY - canvasPosition.top) / canvas.height) * 2 + 1;
rayCaster.setFromCamera(mousePosition, camera);
var intersects = rayCaster.intersectObjects(scene.getObjectByName('MyObj_s').children, true);
if (intersects.length > 0)
return intersects[0].point;
};

get a 2D bounding box from a 3D object

I have a scene filled with ~hundred oblong asteroid shaped objects. I want to place a text label under each one so that the label is visible from any camera angle. I billboard the text so that it always faces the camera.
At first, everything looks great by placing text below the 3d object using .translateY. However, as you start to moving around the scene, labels no longer are 'below' the object depending on your camera position. This especially true when you orient using trackballControls.
How can I place text 'below' the object no matter the orientation. Perhaps if I create a 2d bounding box around each object in relation to the camera on each frame - I could then place the text label right below that 2d box.
I'm also concerned that calculating 2d bounding boxes on a hundred 3d objects every frame could get expensive. Thoughts?
screenshots:
At first, text labels appear correctly translated -y below the object
but as you rotate the camera, labels get sideways
flipping the camera all the way around shows the labels upside down
My goal is to have the labels below the objects no matter the camera orientation.
Did you tried to add the Text Labels to the Object?
object.add(Label) instead of scene.add(Label)
I have a demo site here that might help give you source to look at:
https://dl.dropboxusercontent.com/u/31495717/cubemaker/index.html
This site places textual DOM elements at a screen coordinate-based constant distance from a 3D object, styled with CSS, within the render loop, when the mouse pointer is moved over the 3D object.
From the source:
var id_label = document.createElement('div');
id_label.id = INTERSECTED.name;
id_label.style.position = 'absolute';
id_label.style.top = '-10000px';
id_label.style.left = '-10000px';
id_label.innerHTML = '<span class="particle_label">' + INTERSECTED.name + '<br><span class="particle_sublabel">' + INTERSECTED.subname + '</span></span>';
container.appendChild(id_label);
var id_label_rect = id_label.getBoundingClientRect();
id_label.style.top = (screen_object_center.y - 0.85 * (id_label_rect.height / 2)) + 'px';
if (mouse.x < 0)
id_label.style.left = (screen_object_center.x - horizontal_fudge * (screen_object_edge.x - screen_object_center.x)) + 'px';
else {
id_label.style.left = (screen_object_center.x + horizontal_fudge * (screen_object_edge.x - screen_object_center.x) - id_label_rect.width) + 'px';
id_label.style.textAlign = 'right';
}
The DOM element is drawn offscreen and then repositioned based on attributes of its bounding box and the world coordinates of the 3D element it is associated with. When the mouse pointer is moved outside the 3D element bounds, the text label is removed from the DOM.
Since you are always showing your labels, you might modify this to draw the element once in an initialization step, and simply change the top and left style attributes within the render loop.

Resources