[ACCEPTED]-How to click an object in THREE.js-three.js

Accepted answer
Score: 15

I understand your troubles and I'm here 31 to help. It seems you have one principal 30 question: what operations are performed 29 on the vector to prepare it for click detection?

Let's 28 look back at the original declaration of 27 vector:

var vector = new THREE.Vector3(
    renderer.devicePixelRatio * (event.pageX - this.offsetLeft) / this.width * 2 - 1,
    -renderer.devicePixelRatio * (event.pageY - this.offsetTop) / this.height * 2 + 1,
    0
);
  • renderer.devicePixelRatio relates to a ratio of virtual site pixels / real device pixels
  • event.pageX and .pageY are mouseX, mouseY
  • The this context is renderer.domElement, so .width, .height, .offsetLeft/Right relate to that
  • 1 appears to be a corrective "magic" number for the calculation (for the purpose of being as visually exact as possible)

We don't care about the z-value, THREE 26 will handle that for us. X and Y are our 25 chief concern. Let's derive them:

  1. We first find the distance of the mouse to the edge of the canvas: event.pageX - this.offsetLeft
  2. We divide that by this.width to get the mouseX as a percentage of the screen width
  3. We multiply by renderer.devicePixelRatio to convert from device pixels to site pixels
  4. I'm not sure why we multiply by 2, but it might have to do with an assumption that the user has a retina display (someone can feel free to correct me on this if it's wrong).
  5. 1 is, again, magic to fix what might be just an offset error
  6. For y, we multiply the whole expression by -1 to compensate for the inverted coordinate system (0 is top, this.height is bottom)

Thus you 24 get the following arguments for the vector:

  renderer.devicePixelRatio * (event.pageX - this.offsetLeft) / this.width * 2 - 1,
  -renderer.devicePixelRatio * (event.pageY - this.offsetTop) / this.height * 2 + 1,
  0

Now, for 23 the next bit, a few terms:

  • Normalizing a vector means simplifying it into x, y, and z components less than one. To do so, you simply divide the x, y, and z components of the vector by the magnitude of the vector. It seems useless, but it's important because it creates a unit vector (magnitude = 1) in the direction of the mouse vector!
  • A Raycaster casts a vector through the 3D landscape produced in the canvas. Its constructor is THREE.Raycaster( origin, direction )

With these terms 22 in mind, I can explain why we do this: vector.sub(camera.position).normalize(). First, we 21 get the vector describing the distance from 20 the mouse position vector to the camera 19 position vector, vector.sub(camera.position). Then, we normalize it 18 to make it a direction vector (again, magnitude = 1). This 17 way, we're casting a vector from the camera 16 to the 3D space in the direction of the 15 mouse position! This operation allows us 14 to then figure out any objects that are 13 under the mouse by comparing the object 12 position to the ray's vector.

I hope this 11 helps. If you have any more questions, feel 10 free to comment and I will answer them as 9 soon as possible.

Oh, and don't let the math 8 discourage you. THREE.js is by nature a math-heavy 7 language because you're manipulating objects 6 in 3D space, but experience will help you 5 get past these kinds of understanding roadblocks. I 4 would continue learning and return to Stack 3 Overflow with your questions. It may take 2 some time to develop an aptitude for the 1 math, but you won't learn if you don't try!

Score: 0

This is more universal no matter the render 3 dom location, and the dom and its ancesters's 2 padding margin.

var rect = renderer.domElement.getBoundingClientRect();
mouse.x = ( ( event.clientX - rect.left ) / ( rect.width - rect.left ) ) * 2 - 1;
mouse.y = - ( ( event.clientY - rect.top ) / ( rect.bottom - rect.top) ) * 2 + 1;

here is a demo, scroll to 1 the bottom to click the cube.

<!DOCTYPE html>
<html>
<head>
<script src="http://threejs.org/build/three.min.js"></script>

    <link rel="stylesheet" href="http://libs.baidu.com/bootstrap/3.0.3/css/bootstrap.min.css" />


<style>
body {
    font-family: Monospace;
    background-color: #fff;
    margin: 0px;
}

#canvas {
    background-color: #000;
    width: 200px;
    height: 200px;
    border: 1px solid black;
    margin: 10px;
    padding: 0px;
    top: 10px;
    left: 100px;
}

.border {
    padding:10px; 
    margin:10px;
	height:3000px;
	overflow:scroll;
}

</style>
</head>
<body>
<div class="border">
  <div style="min-height:1000px;"></div>
	<div class="border">
		<div id="canvas"></div>
	</div>
</div>
<script>
// Three.js ray.intersects with offset canvas

var container, camera, scene, renderer, mesh,

    objects = [],
    
    count = 0,

    CANVAS_WIDTH = 200,
    CANVAS_HEIGHT = 200;

// info
info = document.createElement( 'div' );
info.style.position = 'absolute';
info.style.top = '30px';
info.style.width = '100%';
info.style.textAlign = 'center';
info.style.color = '#f00';
info.style.backgroundColor = 'transparent';
info.style.zIndex = '1';
info.style.fontFamily = 'Monospace';
info.innerHTML = 'INTERSECT Count: ' + count;
info.style.userSelect = "none";
info.style.webkitUserSelect = "none";
info.style.MozUserSelect = "none";
document.body.appendChild( info );

container = document.getElementById( 'canvas' );

renderer = new THREE.WebGLRenderer();
renderer.setSize( CANVAS_WIDTH, CANVAS_HEIGHT );
container.appendChild( renderer.domElement );

scene = new THREE.Scene();

camera = new THREE.PerspectiveCamera( 45, CANVAS_WIDTH / CANVAS_HEIGHT, 1, 1000 );
camera.position.y = 250;
camera.position.z = 500;
camera.lookAt( scene.position );
scene.add( camera );

scene.add( new THREE.AmbientLight( 0x222222 ) );

var light = new THREE.PointLight( 0xffffff, 1 );
camera.add( light );

mesh = new THREE.Mesh( 
	new THREE.BoxGeometry( 200, 200, 200, 1, 1, 1 ), 
	new THREE.MeshPhongMaterial( { color : 0x0080ff } 
) );
scene.add( mesh );
objects.push( mesh );

// find intersections
var raycaster = new THREE.Raycaster();
var mouse = new THREE.Vector2();

// mouse listener
document.addEventListener( 'mousedown', function( event ) {
    
 var rect = renderer.domElement.getBoundingClientRect();
mouse.x = ( ( event.clientX - rect.left ) / ( rect.width - rect.left ) ) * 2 - 1;
mouse.y = - ( ( event.clientY - rect.top ) / ( rect.bottom - rect.top) ) * 2 + 1;
  
	raycaster.setFromCamera( mouse, camera );

    intersects = raycaster.intersectObjects( objects );

    if ( intersects.length > 0 ) {
        
        info.innerHTML = 'INTERSECT Count: ' + ++count;
        
    }

}, false );

function render() {

    mesh.rotation.y += 0.01;
    
    renderer.render( scene, camera );

}

(function animate() {

    requestAnimationFrame( animate );

    render();

})();

</script>
</body>
</html>

More Related questions