I want to detect a click event on a three.js sprite here's the code:
function bindEvents(state) {
let intersected;
function onDocumentMouseDown(event) {
event.preventDefault();
const mouseX = (event.clientX / window.innerWidth) * 2 - 1;
const mouseY = - (event.clientY / window.innerHeight) * 2 + 1;
const camera = state.threeD.elements.camera;
const raycaster = state.threeD.raycaster;
raycaster.setFromCamera(new THREE.Vector2(mouseX, mouseY), camera);
var intersects = raycaster.intersectObjects(state.clickables?state.clickables:[]);
if (intersects.length > 0) {
if (intersected) intersected.material.map = intersected.currentMap;
intersected = intersects[0].object;
intersected.currentMap = intersected.material.map;
intersected.material.map = selectCityTexture(128, '#cccccc', 'cccccc');
}
}
document.addEventListener('mousedown', onDocumentMouseDown, false);
document.addEventListener('touchstart', onDocumentMouseDown, false);
}
This almost works, the mouse position is offset by some amount. Probably some calculation error, I have no idea how the above code works please help.
const mouseX = (event.clientX / window.innerWidth) * 2 - 1;
const mouseY = - (event.clientY / window.innerHeight) * 2 + 1;
these are changed with
const mouseX = (event.clientX / canvas.width) * 2 - 1;
const mouseY = - (event.clientY / canvas.height) * 2 + 1;
Related
I have recently been asked to learn about Two.js, and specificaly to modify this example. I have tried for few day to alter this code ..
var two = new Two({
fullscreen: true,
autostart: true
}).appendTo(document.body);
var stage = new Two.Group();
for (var i = 0; i < 100; i++) {
var x = Math.random() * two.width * 2 - two.width;
var y = Math.random() * two.height * 2 - two.height;
var size = 50;
var shape = new Two.Rectangle(x, y, size, size);
shape.rotation = Math.random() * Math.PI * 2;
shape.noStroke().fill = '#ccc';
stage.add(shape);
}
shape.fill = 'red';
shape.position.set(two.width / 2, two.height / 2);
two.add(stage);
addZUI();
function addZUI() {
var domElement = two.renderer.domElement;
var zui = new Two.ZUI(stage);
var mouse = new Two.Vector();
var touches = {};
var distance = 0;
var dragging = false;
zui.addLimits(0.06, 8);
domElement.addEventListener('mousedown', mousedown, false);
domElement.addEventListener('mousewheel', mousewheel, false);
domElement.addEventListener('wheel', mousewheel, false);
domElement.addEventListener('touchstart', touchstart, false);
domElement.addEventListener('touchmove', touchmove, false);
domElement.addEventListener('touchend', touchend, false);
domElement.addEventListener('touchcancel', touchend, false);
function mousedown(e) {
mouse.x = e.clientX;
mouse.y = e.clientY;
var rect = shape.getBoundingClientRect();
dragging = mouse.x > rect.left && mouse.x < rect.right
&& mouse.y > rect.top && mouse.y < rect.bottom;
window.addEventListener('mousemove', mousemove, false);
window.addEventListener('mouseup', mouseup, false);
}
function mousemove(e) {
var dx = e.clientX - mouse.x;
var dy = e.clientY - mouse.y;
if (dragging) {
shape.position.x += dx / zui.scale;
shape.position.y += dy / zui.scale;
} else {
zui.translateSurface(dx, dy);
}
mouse.set(e.clientX, e.clientY);
}
function mouseup(e) {
window.removeEventListener('mousemove', mousemove, false);
window.removeEventListener('mouseup', mouseup, false);
}
function mousewheel(e) {
var dy = (e.wheelDeltaY || - e.deltaY) / 1000;
zui.zoomBy(dy, e.clientX, e.clientY);
}
function touchstart(e) {
switch (e.touches.length) {
case 2:
pinchstart(e);
break;
case 1:
panstart(e)
break;
}
}
function touchmove(e) {
switch (e.touches.length) {
case 2:
pinchmove(e);
break;
case 1:
panmove(e)
break;
}
}
function touchend(e) {
touches = {};
var touch = e.touches[ 0 ];
if (touch) { // Pass through for panning after pinching
mouse.x = touch.clientX;
mouse.y = touch.clientY;
}
}
function panstart(e) {
var touch = e.touches[ 0 ];
mouse.x = touch.clientX;
mouse.y = touch.clientY;
}
function panmove(e) {
var touch = e.touches[ 0 ];
var dx = touch.clientX - mouse.x;
var dy = touch.clientY - mouse.y;
zui.translateSurface(dx, dy);
mouse.set(touch.clientX, touch.clientY);
}
function pinchstart(e) {
for (var i = 0; i < e.touches.length; i++) {
var touch = e.touches[ i ];
touches[ touch.identifier ] = touch;
}
var a = touches[ 0 ];
var b = touches[ 1 ];
var dx = b.clientX - a.clientX;
var dy = b.clientY - a.clientY;
distance = Math.sqrt(dx * dx + dy * dy);
mouse.x = dx / 2 + a.clientX;
mouse.y = dy / 2 + a.clientY;
}
function pinchmove(e) {
for (var i = 0; i < e.touches.length; i++) {
var touch = e.touches[ i ];
touches[ touch.identifier ] = touch;
}
var a = touches[ 0 ];
var b = touches[ 1 ];
var dx = b.clientX - a.clientX;
var dy = b.clientY - a.clientY;
var d = Math.sqrt(dx * dx + dy * dy);
var delta = d - distance;
zui.zoomBy(delta / 250, mouse.x, mouse.y);
distance = d;
}
}
.. in order to make it work this way :
Zoom : on wheel, no change.
Dragging : only if mousedown in space between shapes.
Shape click : execute a function.
I tried many things base on answers found here and doc, but nothing work at all, an some pieces of code could be seen as a damn blasphemy.
I don't ask for a complete working code (if someone can provide it I will study it gladly), but any explanations or hints will be greatly appreciate.
I've been having issues with three.js raycasting recently.
I have a div element for which the element is not the full screen and it should not be the full screen and I think this is causing issues with the positioning of the raycast which I don't know how to fix.
let mouseVector = new THREE.Vector3()
let raycaster = new THREE.Raycaster()
Object.assign(mouseVector, {
x: 2 * (e.clientX / canvas.width) - 1,
y: 1 - 2 * (e.clientY / canvas.height)
})
raycaster.setFromCamera(mouseVector, camera)
let intersects = raycaster.intersectObject(scene.children)
for(const intersection of intersects) {
console.log(intersection)
}
mouse.x = ( e.clientX / window.innerWidth ) * 2 - 1;
mouse.y = - ( e.clientY / window.innerHeight ) * 2 + 1;
raycaster.setFromCamera( mouse, camera );
intersects = raycaster.intersectObjects( scene.children, true );
console.log(intersects);
for(const ray of intersects) {
if(ray.object.name !== "Grid") {
if(smallestDist == undefined){
smallestDist1 = ray;
}else if(smallestDist > ray.distance){
smallestDist1 = ray
}
}
}
mouse.x = ( e.clientX / window.innerWidth ) * 2 - 1;
mouse.y = - ( e.clientY / window.innerHeight ) * 2 + 1;
raycaster.setFromCamera( mouse, camera );
var intersects = raycaster.intersectObjects( scene.children );
for ( var i = 0; i < intersects.length; i++ ) {
if(intersects[i].object.name == "Grid"){
}else{
if(smallestDist == undefined){
smallestDist = intersects[i];
}else if(smallestDist > intersects[i].distance){
smallestDist = intersects[i]
}
}
}
console.log(smallestDist1)
if(smallestDist1){
const geometry = new THREE.SphereGeometry( 0.5, 15, 15 );
const material = new THREE.MeshBasicMaterial( {color: 0xFF0000} );
const sphere = new THREE.Mesh( geometry, material );
sphere.position.set(smallestDist1.point.x, smallestDist1.point.y, smallestDist1.point.z)
sphere.name = "TestSphere"
scene.add( sphere );
}
Above is the code i've tried however as the element with the 3D object isn't full window sized I think it's bugging out. Any help is appreciated.
Try to compute mouse like so:
const rect = renderer.domElement.getBoundingClientRect();
const x = event.clientX - rect.left;
const y = event.clientY - rect.top;
mouse.x = ( x / canvas.clientWidth ) * 2 - 1;
mouse.y = ( y / canvas.clientHeight) * - 2 + 1
The top-left point of the browser window is the origin of coordinate (0, 0) for MouseEvent, while the exact center point of canvas for threejs.
Click here to see coordinate details.
As shown the image above, four corner points of the canvas is equivalent to (-1, 1), (1, 1), (1, -1), (-1, -1) under threejs context.
Otherwise, the canvas might have offsets to the browser (0, 0), which can be accessed with canvas.offsetTop and cavas.offsetLeft.
With all above considered, any MouseEvent client coordinate (event.clientX, event.clientY) should be mapped to threejs coordinate as below:
mouse.x = ( (event.clientX - canvas.offsetLeft) / canvas.clientWidth ) * 2 - 1;
mouse.y = ( (event.clientY - canvas.offsetTop) / canvas.clientHeight ) * -2 + 1;
I have a group of clickable objects that can be spun either using the scroll wheel or by clicking and dragging. The problem I am having is when you drag and then release on top of an object, it will launch the click animation.
Here is a link to a fiddle of the simplified project.
Does anyone know how to stop the click from being recorded during the drag? It seems like its being a bit more difficult because they are raycasted objects.
My js functions:
document.addEventListener("mousedown", function () {
isMouseDown = true
startX = event.pageX
startY = event.pageY
document.body.style.cursor = 'grabbing';
})
document.addEventListener("mouseup", function () {
isMouseDown = false
document.body.style.cursor = 'grab';
})
document.addEventListener("mousemove", function (event) {
if (isMouseDown) {
document.body.style.cursor = 'grabbing'
}
aimX = ((window.innerWidth / 2) - event.pageX) * 0.35
aimY = ((window.innerHeight / 2) - event.pageY) * 0.5
mouse.x = ( event.clientX / window.innerWidth ) * 2 - 1;
mouse.y = - ( event.clientY / window.innerHeight ) * 2 + 1;
raycaster.setFromCamera(mouse, camera)
const intersections = raycaster.intersectObjects(group.children)
if (intersections.length > 0) {
if(projectHov){
if (INTERSECTED != intersections[0].object) {
if (INTERSECTED)
INTERSECTED.material.color.setHex(INTERSECTED.currentHex);
INTERSECTED = intersections[0].object;
INTERSECTED.currentHex = INTERSECTED.material.color.getHex();
//setting up new material on hover
INTERSECTED.material.color.setHex( 0xadadad );
if (INTERSECTED){
if(projectHov){
document.body.style.cursor = "pointer"
}
}
}
}
} else {
if (INTERSECTED) INTERSECTED.material.color.setHex(INTERSECTED.currentHex);
document.body.style.cursor = "grab";
INTERSECTED = null;
}
// Moving the objects ==========================
if(isMouseDown) {
let currentRotation = new THREE.Matrix4();
currentRotation.makeRotationFromEuler(group.rotation);
let newEuler = new THREE.Euler(0, ((event.pageX - startX) + (event.pageY - startY)) / 325, 0);
let newRotation = new THREE.Matrix4();
newRotation.makeRotationFromEuler(newEuler);
let finalRotation = new THREE.Matrix4();
finalRotation.multiplyMatrices(newRotation, currentRotation);
group.rotation.setFromRotationMatrix(finalRotation);
startX = event.pageX;
startY = event.pageY;
}
})
section.addEventListener('click', onDocumentMouseDown, false);
function onDocumentMouseDown(event) {
event.preventDefault();
mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;
raycaster.setFromCamera(mouse, camera);
const intersections = raycaster.intersectObjects(objects)
// Animation =================================
if (intersections.length > 0){
if(projectHov){
tl.play();
tl.to(backTag, 1, {
delay: 1.25,
autoAlpha:1,
}, 0);
}
// Animation ===================================
if (intersections[0].object == old ) {
if(projectHov){
tlOld.play();
projectHov = false
tlOld.to(old, 1.5, {three:{scaleX: 1.5, scaleY: 1.5, x:0, y:0, z:0}, ease:Power2.easeInOut}, 0);
tlOld.to(fnup, 1, {three:{y:-4000, opacity: 0 }, ease:Power2.easeInOut}, 0);
tlOld.to(alex, 1, {three:{y:-4000, opacity: 0 }, ease:Power2.easeInOut}, 0);
tlOld.to(cam, 1, {three:{y:-4000, opacity: 0 }, ease:Power2.easeInOut}, 0);
tlOld.to(mirrorCube, 1, {three:{y:-400, opacity: 0 }, ease:Power2.easeInOut}, 0);
groupRotate = false
}
}
You may want to store intersections[0].object.uuid; in a variable and update it on mousedown and mouseup. Then compare stored uuid with raycasted object uuid on mouse up. Process further only if they are equal.
I have an annoying problem where my raycasting is fine with the mouse but not with touch.
I setup events as such
$(document).bind('mousedown', onDocumentMouseDown);
$(document).bind('mousewheel', onDocumentMouseWheel);
$(document).bind('touchstart', onDocumentTouchStart);
$(document).bind('touchmove', onDocumentTouchMove);
My mouse event
function onDocumentMouseDown(e) {
e.preventDefault();
detectHotspotClick(e.pageX,e.pageY);
}
My Touch event
function onDocumentTouchStart(e) {
e.preventDefault();
var event = e.originalEvent;
if (event.touches.length == 1) {
var t=event.touches[0];
draggingY = t.pageY;
detectHotspotClick(t.pageX,t.pageY);
}
if (event.touches.length == 2) {
var dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX;
var dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY;
_touchZoomDistanceEnd = _touchZoomDistanceStart = Math.sqrt( dx * dx + dy * dy );
}
}
My detection
function detectHotspotClick(x,y){
console.log('detectHotspotClick('+ x + "," + y+ ")" );
var raycaster = new THREE.Raycaster();
var mouse = new THREE.Vector2();
mouse.x = ( x / renderer.domElement.width ) * 2 - 1;
mouse.y = - ( y / renderer.domElement.height ) * 2 + 1;
raycaster.setFromCamera( mouse, camera );
var intersects = raycaster.intersectObjects( targetList, true );
if ( intersects.length > 0)
hotspotClick(intersects[ 0 ].object);
}
This works fine with mouse, but I notice on Touch, the mouse.x value always seems to be negetative and I'm not sure that's right.
Is there any other way of doing this I should consider?
Not sure why.. but it works with detectHotspotClick(t.pageX*2,t.pageY*2) on the touchevent, which must report different figures to the mouse event.
I'm trying to get a good mouse movement in my scene so I can rotate around the object.
I have two problems, I can figure out how to limit the movement so that it never rotates below 0 degrees on the Y axis. (I dont want to see the object from below, only above)
And the second thing I can't figure out is how to make the movement smooth. Now with what I have achieved in the jsfiddle is that the camera moves back to its starting position before starting to rotate.
My try: http://jsfiddle.net/phacer/FHD8W/4/
This is the part I dont get:
var spdy = (HEIGHT_S / 2 - mouseY) / 100;
var spdx = (WIDTH / 2 - mouseX) / 100;
root.rotation.x += -(spdy/10);
root.rotation.y += -(spdx/10);
What I want without using an extra library: http://www.mrdoob.com/projects/voxels/#A/afeYl
You can rotate you scene with this code,
To ensure to not rotate under 0, simule rotation of a vector (0,0,1) and check if y of vector is negative
var mouseDown = false,
mouseX = 0,
mouseY = 0;
function onMouseMove(evt) {
if (!mouseDown) {
return;
}
evt.preventDefault();
var deltaX = evt.clientX - mouseX,
deltaY = evt.clientY - mouseY;
mouseX = evt.clientX;
mouseY = evt.clientY;
rotateScene(deltaX, deltaY);
}
function onMouseDown(evt) {
evt.preventDefault();
mouseDown = true;
mouseX = evt.clientX;
mouseY = evt.clientY;
}
function onMouseUp(evt) {
evt.preventDefault();
mouseDown = false;
}
function addMouseHandler(canvas) {
canvas.addEventListener('mousemove', function (e) {
onMouseMove(e);
}, false);
canvas.addEventListener('mousedown', function (e) {
onMouseDown(e);
}, false);
canvas.addEventListener('mouseup', function (e) {
onMouseUp(e);
}, false);
}
function rotateScene(deltaX, deltaY) {
root.rotation.y += deltaX / 100;
root.rotation.x += deltaY / 100;
}