Clickable points on Three.js globe - three.js

I have looked into using three.js but could not find any examples specific to what I need.
I am trying to create a 3D Map (on a globe) that has 3 different pin points around the world. I'd like to be able to detect which pinpoint was clicked on, in order to display information elsewhere on the page.
Is anyone aware of a way to do this? Any demos or tutorials would be very helpful.
Thank you.

Use an event listener to fire off a raycaster on mouse click. I quickly cut and pasted some code from one of my projects so that it does a raycast from the mouse in screenspace to an object in 3D space and updates the color of the material to red.
This should at least get you started in the right direction:
var mouse = new THREE.Vector2(0,0);
var raygun = new THREE.Raycaster();
var useRaycast = true;
// Raycast when we click the mouse
function onClick() {
// Get mouse position in screen space
mouse.x = ( event.clientX / window.innerWidth ) * 2 - 1;
mouse.y = - ( event.clientY / window.innerHeight ) * 2 + 1;
// Only raycast if not panning (optimization)
var hits;
if (useRaycast) {
raygun.setFromCamera(mouse, camera);
// Raycast to single object
hits = raygun.intersectObject(myTargetObect, false);
// Raycast to multiple objects
// hits = raygun.intersectObjects([myTargetObect, myTargetObect2]);
}
// Run if we have intersections
if (hits.length > 0) {
for (var i = 0; i < hits.length; i++ ) {
// Change material color of item we clicked on
hits[i].object.material.color.set(0xff0000);
}
renderer.render(scene, camera);
}
}
window.addEventListener('click', onClick, false);
MrDoob also has an example on github with a web demo.

Related

How to enable/ disable element by clicking on the sprite element in three.js

I have three sprites in the group groupIcons.add(icon1, icon2, icon3); and three planes in another group groupPhotos.add(photo1, photo2, photo3);. These groups are THREE.group objects.
The goal is if icon1 is clicked to perform scene.add(photo1), if icon2 is clicked to perform scene.add(photo2).
I found many documentations on how to change colors or to drag objects but not the solution to my problem.
I would appreciate any suggestion.
My code is down below.
const groupIcons = new THREE.Group();
groupIcons.add(icon1, icon2, icon3);
scene.add( groupIcons);
const groupPhotos = new THREE.Group();
groupPhotos.add(photo1, photo2, photo3);
// Then I want something like this
raycaster = new THREE.Raycaster();
pointer = new THREE.Vector2();
window.addEventListener('click', clickIcons);
function clickIcons(event) {
pointer.set((event.clientX / window.innerWidth) * 2 - 1, - (event.clientY / window.innerHeight) * 2 + 1);
raycaster.setFromCamera( pointer, camera );
const intersects = raycaster.intersectObjects( groupIcons, true);
if ( intersects.length > 0 ) {
// if (ray intersects icon1) {
// scene.add(photo1)
// if (ray intersects icon2) {
// scene.add(photo2)
// and so on
// I also want to hide photos elements when these icons are clicked (something like toggle)
}
https://threejs.org/docs/?q=Object#api/en/core/Object3D.visible
Sprite has an visible property.
sprite.visible = false;
example: https://jsfiddle.net/mqtvjzsr/2/

Rotate webGL canvas to appear landscape-oriented on a portrait-oriented mobile phone

I’m using a-frame and trying to accomplish this task - force the canvas to be rendered as “landscape” when a mobile device is in portrait orientation (ie. device-width = 414px and device-height = 736px).
I have successfully accomplished this with the following steps
camera.aspect = 736 / 414;
camera.updateProjectionMatrix();
renderer.setSize(736, 414);
In css...
.a-canvas {
transform: rotate(90deg) translate(161px, 161px);
height: 414px !important;
width: 736px !important;
}
This all works great except for one major thing…I have 3D buttons in my scene and when I go to click them they don’t line up with the rotated canvas, instead their clickable position remains in the same place as before the canvas was rotated.
I’ve tried to set matrixWorldNeedsUpdate = true on the scene’s object3D along with updateWorldMatrix() with no luck. I tried calling refreshObjects on the raycaster with no luck. I tried rotating the scene and the camera with no luck.
I’m not sure what else to do. Any help would be greatly appreciated!
ANSWER:
Thanks to Marquizzo and gman for the help. Here's the updated a-frame source code (v1.0.4) to make the raycaster handle this forced landscape canvas properly
// line: 66884
onMouseMove: (function () {
var direction = new THREE.Vector3();
var mouse = new THREE.Vector2();
var origin = new THREE.Vector3();
var rayCasterConfig = {origin: origin, direction: direction};
return function (evt) {
var bounds = this.canvasBounds;
var camera = this.el.sceneEl.camera;
var left;
var point;
var top;
camera.parent.updateMatrixWorld();
// Calculate mouse position based on the canvas element
if (evt.type === 'touchmove' || evt.type === 'touchstart') {
// Track the first touch for simplicity.
point = evt.touches.item(0);
} else {
point = evt;
}
left = point.clientX - bounds.left;
top = point.clientY - bounds.top;
// mouse.x = (left / bounds.width) * 2 - 1;
// mouse.y = -(top / bounds.height) * 2 + 1;
// HAYDEN's CODE: flipping x and y coordinates to force landscape
// --------------------------------------------------------------
let clickX = (left / bounds.width) * 2 - 1;
let clickY = - (top / bounds.height) * 2 + 1;
mouse.x = -clickY;
mouse.y = clickX;
// --------------------------------------------------------------
origin.setFromMatrixPosition(camera.matrixWorld);
direction.set(mouse.x, mouse.y, 0.5).unproject(camera).sub(origin).normalize();
this.el.setAttribute('raycaster', rayCasterConfig);
if (evt.type === 'touchmove') { evt.preventDefault(); }
};
})(),
A-Frame uses a Raycaster internally to determine if the spot you clicked has hit an object. You can see in the Three.js documentation the raycaster needs the mouse x&y coordinates to determine where you clicked. Here's a working demo of that concept. However with your setup, x, y turns into -y, x.
I think you'll have to write your own Raycaster function to trigger on the click event instead of relying on the built-in AFrame functionality, and then swap the x&y values:
function onClick() {
let clickX = ( event.clientX / window.innerWidth ) * 2 - 1;
let clickY = - ( event.clientY / window.innerHeight ) * 2 + 1;
mouse.x = -clickY;
mouse.y = clickX;
// Then continue with raycaster.setFromCamera(), etc...
}
window.addEventListener( 'click', onClick, false );

Event issues when rendering three js on a div

I built my scene in three js and everything was working properly when I rendered the scene on the . But now I need the scene to be on a div, 70% of the viewport to be specific, the other 30% being a left side panel with some info. I replaced every window.height and width for the width and height of the div itself, but now the events are all broken. I tried bubbling and capturing but the problem seems to be related with the camera or something in three js, because when I hover on a corner something is happening...but nothing is happening when i hover/click on the 3d globe. I google everything and didn't find an answer or people with similar issues..I'm hopeless, help? Thanks.
Edit: here's a fiddle: https://jsfiddle.net/2L686cnw/
Did you update all for the renderer relevant variables on window resize?
Just like this:
var onWindowResize = function(){
renderWidth = element.offsetWidth;
renderHeight = element.offsetHeight;
camera.aspect = renderWidth/renderHeight;
camera.updateProjectionMatrix();
renderer.setSize(renderWidth, renderHeight);
};
$(window).on('resize', function() {
onWindowResize();
});
The issue sounds like that you get wrong viewport coordinates for your click position. Most three.js examples add event listeners to window or document object and using event.clientX (and clientY) to transform the mouse screen coordinates to viewport coordinates to use it for raycasting.
Having the 3D scene within a separate div covering only part of the page, it should be a bit different:
function onClick(event) {
var mouse = new THREE.Vector2();
mouse.x = (event.offsetX / panelWidth) * 2 - 1;
mouse.y = - (event.offsetY / panelHeight) * 2 + 1;
// do raycasting
}
canvas.addEventListener('click', onClick);

zooming based on the position of cursor in mousewheel

I am currently working on a THREE.js project.While using trackball control zooming it is zooming relative to the center of the model.But i want to zoom according to the position of cursor with mouse wheel.Any one who is familiar please help me over this.
Thank you in advance.
In mousewheel add this code which helps you in zooming based on the cursor position and not relative to the model.
If you are using trackball controls,you could use that for panning and rotating.
So set trackball controls enabled to false in mousewheel.
renderer.domElement.addEventListener('mousewheel',
function (e) {
mousewheel(e);
},
false);
function mousewheel(event) {
trackBallControls.noZoom = true;
event.preventDefault();
var factor = 50;
var mX = (event.clientX / jQuery(container).width()) * 2 - 1;
var mY = -(event.clientY / jQuery(container).height()) * 2 + 1;
var vector = new THREE.Vector3(mX, mY, 0.5);
//console.log(vector);
vector.unproject(camera);
vector.sub(camera.position);
if (event.deltaY < 0) {
camera.position.addVectors(camera.position,
vector.setLength(factor));
trackBallControls.target.addVectors(trackBallControls.target,
vector.setLength(factor));
camera.updateProjectionMatrix();
} else {
camera.position.subVectors(camera.position,
vector.setLength(factor));
trackBallControls.target.subVectors(trackBallControls.target,
vector.setLength(factor));
camera.updateProjectionMatrix();
}
}

How to get a point coordinate on a SkinnedMaterial for the human demo of three.js?

For the human demo http://threejs.org/examples/#webgl_morphtargets_human , I wonder how to get a point on the skin of the human. For example when I click some place on the human body, what's the coordinate of that place?
I tried using the raycaster to get that but in vain.The code is like this:
var projector;
init() {
// Others
// ...
projector = new THREE.Projector();
renderer.domElement.addEventListener('mouseup', onMouseUp, false);
}
function onMouseUp(e) {
e.preventDefault();
var vector = new THREE.Vector3(
( event.clientX / window.innerWidth ) * 2 - 1,
- ( event.clientY / window.innerHeight ) * 2 + 1,
0.5
);
projector.unprojectVector( vector, camera );
var raycaster = new THREE.Raycaster( camera.position, vector.sub( camera.position ).normalize() );
var intersections = raycaster.intersectObjects( character.root.children );
if (intersections.length > 0) {
debugger;
// ...
}
}
But the intersections is always empty.
Three.js is r67
Thanks in advance.
I'm new to three.js, and i've also tried to draw plots on the human. I manage to do it, but it's not on the "visible" body. In fact, you should first use the intersect method with the recursive arg :
var intersections = raycaster.intersectObjects( scene.children, true );
Thus, you 'll be able to interact with the objects composing the body, but they are not positioned under the "skin". It seems to be that they have been "moved", because you can interact with them if you aim in front of the feet of the body. Unfortunately, I don't know for the moment why, and how to interact with their "visible representation".
Well, finally I find that it's just because the human animation. It works if I comment the animation out.

Resources