如何单击THREE.js中的对象

时间:2014-05-18 16:01:12

标签: three.js

我正在通过this book工作,我想我做得还可以,但我已经找到了一些我并不真正得到的东西。

以下是您如何登录单击的3D空间中的控制台和对象:

renderer.domElement.addEventListener('mousedown', function(event) {
    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
    );

    projector.unprojectVector(vector, camera);

    var raycaster = new THREE.Raycaster(
        camera.position,
        vector.sub(camera.position).normalize()
    );

    var intersects = raycaster.intersectObjects(OBJECTS);
    if (intersects.length) {
        console.log(intersects[0]);
    }
}, false);

以下是本书对此代码如何运作的解释:

  

前面的代码侦听渲染器画布上的mousedown事件。

得到它,我们使用renderer.domElement找到渲染器正在使用的domElement。然后,我们使用addEventListner将事件监听器绑定到它,并指定我们要监听mousedown。单击鼠标时,我们启动匿名函数并将event变量传递给函数。

  

然后,   它使用屏幕上的鼠标坐标创建一个新的Vector3实例   相对于画布中心的百分比,画布宽度。

什么?我了解了我们如何使用new THREE.Vector3创建新实例,并且我认为Vector3所采用的三个参数是x, y and z坐标,但这就是我理解的地方完全彻底地崩溃了。

首先,我在这里做了一个假设,但为了绘制一个矢量,你必须在空间中需要两个点来投射?如果你只给它一套坐标,它怎么知道投射的方向?我的猜测是你实际上使用Raycaster来绘制"向量" ...

现在我们转到Vector3的论点......我知道z是0的原因。因为我们只对我们点击的位置感兴趣屏幕。我们可以向上或向下,向左或向右单击,但不能进入或退出屏幕,因此我们将其设置为零。现在让我们解决x:

renderer.devicePixelRatio * (event.pageX - this.offsetLeft) / this.width * 2 - 1,

我们正在获取设备的PixelRatio,按照我们沿x轴点击的位置进行定时,除以渲染器的domElement宽度,将其除以2并取走一个。

如果你没有得到什么,你需要说出你的所作所为,这样人们才能最好地帮助你。所以当我说:

时,我觉得这样的傻瓜
  • 我不知道为什么我们甚至需要像素比率我不知道为什么我们按照我们点击x的位置来计算
  • 我不知道为什么我们将宽度除以
  • 完全不明白为什么我们需要时间到2并带走1.时间2,带走1.这真的可能是大象的时间,带走花生和它会有所帮助。

我得到y甚至更少:

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

为什么我们现在随机使用- devicePixelRatio?为什么现在决定add一个而不是minus一个?

  

然后,相对于相机,该矢量被非投影(从2D到3D空间)。

什么?

  

一旦我们在3D空间中有一个代表鼠标位置的点,   我们使用Raycaster画一条线。它的两个论点   接收是起点和结束点的方向。

好的,我明白了,这就是我上面提到的。我们如何需要两点来绘制"向量"。在三个谈话中,一个向量似乎被称为" raycaster"。

然而,我们作为论点传递给它的两点并没有多大意义。如果我们传递相机的position和矢量position并从这两点中得出投影,我就会得到它,事实上我们正在使用camera.position代表第一点,但

vector.sub(camera.position).normalize()

为什么我们减去camera.position?我们为什么要正常化?为什么这本无用的书不想解释什么?

  

我们通过减去鼠标和相机位置来获得方向   然后规范化结果,将每个维度除以   向量的长度以缩放它,以便没有维度具有值   大于1。

什么?我不是懒惰,而不是by过去的一句话。

  

最后,我们使用光线检查哪些物体位于   使用intersectObjects给定方向(即在鼠标下)   方法。 OBJECTS是要检查的对象数组(通常是网格);是   一定要为你的代码适当地改变它。一组对象   返回鼠标后面并按距离排序,所以   第一个结果是被点击的对象。中的每个对象   intersects数组有一个object,point,face和distance属性。   这些属性的值分别是单击的对象   (通常是一个Mesh),一个表示被点击的Vector3实例   空间位置,点击位置的Face3实例,以及   从相机到点击点的距离。

我明白了。我们抓住矢量通过的所有对象,将它们按距离顺序放入数组并记录第一个,即最近的一个:

console.log(intersects[0]);

老实说,你认为我应该放弃三个吗?我的意思是,我肯定已经掌握了它,我理解它的所有编程方面,创建新实例,使用数据对象,如数组,使用匿名函数和传入变量,但每当我点击数学我似乎是在soul soul soul。。。。。。。。。。。

或者这真的很难吗?你觉得这很棘手吗?只是这本书并不觉得有必要详细解释,other answers也没有,好像这些东西对大多数人来说都是正常的。我觉得自己像个白痴。我应该放弃吗?我想制作3D游戏。我真的非常想,但我被创造整个世界的诗意所吸引。不是数学。如果我说我没有找到数学困难,我会说谎。

2 个答案:

答案 0 :(得分:13)

我理解你的烦恼,我来这里提供帮助。您似乎有一个主要问题:对矢量执行哪些操作以准备点击检测?

让我们回顾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与虚拟网站像素的比率相关/ 真实设备像素
  • event.pageX.pageY是mouseX,mouseY
  • this上下文为renderer.domElement,因此.width, .height, .offsetLeft/Right
  • 相关联
  • 1似乎是一种矫正"魔法"计算的数字(为了尽可能在视觉上准确)

我们不关心z值,THREE会为我们处理这个问题。 X和Y是我们最关心的问题。让我们推导出来:

  1. 我们首先找到鼠标到画布边缘的距离:event.pageX - this.offsetLeft
  2. 我们将其除以this.width以获得mouseX占屏幕宽度的百分比
  3. 我们乘以renderer.devicePixelRatio以将设备像素转换为网站像素
  4. 我不确定为什么我们会乘以2,但这可能与用户有视网膜显示的假设有关(如果有人可以随时纠正我的话) ;错了)。
  5. 再次,
  6. 1可以修复可能只是偏移的错误
  7. 对于y,我们将整个表达式乘以-1以补偿倒置坐标系(0为顶部,this.height为底部)
  8. 因此,您可以获得向量的以下参数:

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

    现在,对于下一位,几个术语:

    • 规范化向量意味着将其简化为小于1的x,y和z分量。为此,您只需将向量的x,y和z分量除以向量的大小即可。它似乎毫无用处,但它很重要,因为它在鼠标矢量的方向上创建了一个单位矢量(幅度= 1)!
    • Raycaster 通过画布中生成的3D横向投射矢量。它的构造函数是THREE.Raycaster( origin, direction )

    考虑到这些条款,我可以解释为什么我们这样做:vector.sub(camera.position).normalize()。首先,我们得到描述从鼠标位置矢量到相机位置矢量vector.sub(camera.position)的距离的矢量。然后,我们将其标准化以使其成为direction向量(同样,幅度= 1)。这样,我们就可以在鼠标位置的方向上将矢量从相机投射到3D空间!此操作允许我们通过将对象位置与光线的矢量进行比较来找出鼠标下的任何对象。

    我希望这会有所帮助。如果您还有其他问题,请随时发表评论,我会尽快回答。

    哦,不要让数学劝阻你。 THREE.js本质上是一种数学密集型语言,因为您在3D空间中操纵对象,但经验将帮助您克服这些理解障碍。我会继续学习并回答Stack Overflow的问题。培养数学能力可能需要一些时间,但如果你不尝试,你就不会学习!

答案 1 :(得分:0)

无论渲染dom位置,dom及其祖先的填充边距,这都是更普遍的。

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;

这是一个演示,滚动到底部点击立方体。



<!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>
&#13;
&#13;
&#13;