当前位置: 首页 > 编程日记 > 正文

如何实现对象交互

在本篇随笔中,我们学习下什么是对象选择,投影和反投影是如何工作的,怎样使用Three.js构建可使用鼠标和对象交互的应用。例如当鼠标移到对象,对象变成红色,鼠标移走,对象又恢复原来的颜色。

本篇随笔的源代码来自于:https://github.com/sole/three.js-tutorials/tree/master/object_picking

这里还有更多的例子可供参考:

和立方体交互,当你在立方体盒子上点击,鼠标和立方体交互的点会出现一个黑点;

画布交互,你可以增加方格到画布上,也可以移出它;

当你在操作这些例子,会防线它们有一个共同特性。我们使用的2D坐标系(屏幕)检测3D空间中的对象。这就是对象的选择。

1.如何工作

在写代码之前,了解计算机3D图形是如何工作是非常有帮助的,即使是非常粗糙的方式。我们如何从抽象的3D场景映射到我们屏幕中的2D图像?

当你使用相机渲染场景时,一大堆数据机制开始对3D场景进行运算和处理,以便生成可从相机看到的场景片段的2D表示。有许多步骤涉及,但我们这里感兴趣的是投影,就是它将3D的对象变成了屏幕中的2D实体。那如果反向操作又是怎样的?

为什么需要知道反向操作?你可能会提这样的问题。那么,如果你想知道你的鼠标指针下面是哪个对象,你需要把这些2D坐标重新转换到3D坐标中,然后才能确定是选择的那个3D对象。这叫做“反投影”。所有得到以下两个定义:

Porjection:从3D到2D的投影。

UnProjection:反投影,从2D反向投影到3D。

这里还许绍一个步骤:一旦我们反向投影到3D坐标中,我们怎么确认是否选中了某个对象?答案是:投射光线。我们从3D鼠标的位置投射一根光线,沿着相机的当前方向,看射线是否投射到任何对象上。如果是,那么我们就选中了某个对象。没有,那么我们就没选中如何对象。

听起来有些疑惑。我们看看下面的图片:

firing rays!

图片中,左边是一个抽象的3D场景,包含两个立方体和一个金字塔。中间代表了我们的屏幕。在屏幕上可看到我们的目标位置。右边是我们视线角度的摄像头,另外还有一条蓝色射线,使用它来选择对象。

以上的介绍,让我们简单了解了选择对象的理论原理,接下来我们看看在three.js中是如何体现这样的过程。

对象选择代码实现

在thres.js中实现这种的功能是非常简单的。我们先创建场景、渲染器、摄像头等:

var container = document.getElementById( 'container' ),containerWidth, containerHeight,renderer,scene,camera;containerWidth = container.clientWidth;
containerHeight = container.clientHeight;renderer = new THREE.CanvasRenderer();
renderer.setSize( containerWidth, containerHeight );
container.appendChild( renderer.domElement );renderer.setClearColorHex( 0xeeeedd, 1.0 );scene = new THREE.Scene();camera = new THREE.PerspectiveCamera( 45, containerWidth / containerHeight, 1, 10000 );
camera.position.set( 0, 0, range * 2 );
camera.lookAt( new THREE.Vector3( 0, 0, 0 ) );

上面的代码都非常简单,没什么可介绍的。接着,我们添加一些对象。我们创建灰色立方体并且把他们随机设置他们的3D坐标。我把这些所有的对象都存放在一个类型为Object3D对象中。

geom = new THREE.CubeGeometry( 5, 5, 5 );cubes = new THREE.Object3D();
scene.add( cubes );for(var i = 0; i < 100; i++ ) {var grayness = Math.random() * 0.5 + 0.25,mat = new THREE.MeshBasicMaterial(),cube = new THREE.Mesh( geom, mat );mat.color.setRGB( grayness, grayness, grayness );cube.position.set( range * (0.5 - Math.random()), range * (0.5 - Math.random()), range * (0.5 - Math.random()) );cube.rotation.set( Math.random(), Math.random(), Math.random() ).multiplyScalar( 2 * Math.PI );cube.grayness = grayness; // *** NOTE THIS
        cubes.add( cube );
}

所有的集合对象都使用同一个材质,它这些材质的颜色是不同的,每个立方体都设置了一种随机的灰度颜色。接下来,我们准备两个关键对象:射线对象、鼠标坐标。

var raycaster = new THREE.Raycaster();
var mouseVector = new THREE.Vector3();

当鼠标移动时,我们想选择对象。所以需要监听mousemove事件:

window.addEventListener( 'mousemove', onMouseMove, false );

然后,所有感兴趣的功能都会在这个事件里边实现。当查看源代码使,你需要特别小心下边两行代码,这两天代码稍有差错,可能我们后面的选择功能将无法实现:

mouseVector.x = 2 * (e.clientX / containerWidth) - 1;
mouseVector.y = 1 - 2 * ( e.clientY / containerHeight );

这两行代码将鼠标坐标转换为 x、y范围在(-1, 1)的笛卡尔坐标。你可能主要到计算的y坐标为什么是负的?那是因为经典的DOM坐标系原点(0,0)是从左上角开始。往右是x轴,往下是y坐标。但笛卡尔坐标的却如下所示:

image

理解了这两个坐标系,上面的代码你就知道为什么会那样写了。

现在我们将使用mouseVector和camera生成具体的摄像方向:

raycaster.setFromCamera(mouseVector.clone(), camera);

这里我们克隆了mouseVector,而表示直接传递它。那是因为setFromCamera函数内部会修改mouseVector的值,你可以查看three.js源代码看看,是否真的有修改。创建raycaster对象之后,我们调用它的intersectObjects函数:

var intersects = raycaster.intersectObjects( cubes.children );

传递的参数为cubes.chidren,也就是说我们要选择的对象来自于cubes的children中。intersects将返回查询一个选中对象的集合。并且某个对象包含了以下属性:

distance:摄像头和对象有距离。

point:在对象上表面上和射线交互的点的位置。

face:对象和射线交互的面。

object:和射线交互的对象。

既然已经获取到这些对象了,那么我们也可以操作这些对象。首选我们把所有对象的颜色复原为之前设置的灰色:

cubes.children.forEach(function( cube ) {cube.material.color.setRGB( cube.grayness, cube.grayness, cube.grayness );
});

接着我们再设置交互的对象。把这些对象的颜色设置成红色。

for( var i = 0; i < intersects.length; i++ ) {var intersection = intersects[ i ],obj = intersection.object;obj.material.color.setRGB( 1.0 - i / intersects.length, 0, 0 );
}

以上就是OnMouseMove函数的所有代码了,通过这些代码我们初步了解了选择对象操作,其实我们要写的代码很少,three.js已经帮我们实现了具体的步骤。

涉及到的鼠标操作功能很多,选择对象是最基础的,万变不离其宗。像对象的拖动功能,选择也是基础功能。接下来我们就再看看three.js是如何实现拖拽功能的。

three.js实现拖拽功能

实现拖拽功能,主要使用了three.js的两个扩展控件:TrackballControls和DragControls。

首先,我们先创建随机位置的200个立方体:

var objects = [];var geometry = new THREE.BoxGeometry(40, 40, 40);for(var i = 0; i < 200; i++){var object = new THREE.Mesh(geometry, new THREE.MeshLambertMaterial({color: Math.random() * 0xffffff}));object.position.set(Math.random() * 1000 - 500, Math.random() * 600 - 300, Math.random() * 800 - 400);object.rotation.set(Math.random() * 2 * Math.PI, Math.random() * 2 * Math.PI, Math.random() * 2 * Math.PI);object.scale.set(Math.random() * 2 + 1, Math.random() * 2 + 1, Math.random() * 2 + 1);object.castShadow = true;object.receiveShadow = true;scene.add(object);objects.push(object);}

为了然各个立方体显示随机,每个object都使用Math.random()函数随机设置了position、rotation、scale。并且对象可产生投影可接收投影。

接下来我们创建刚才提到的两个控件:

var controls = new THREE.TrackballControls(camera);controls.rotateSpeed = 1.0;controls.zoomSpeed = 1.2;controls.panSpeed = 0.8;controls.noZoom = false;controls.noPan = false;controls.staticMoving = true;controls.dynamicDampingFactor = 0.3;
var dragControls = new THREE.DragControls(objects, camera, webGLRenderer.domElement);

TrackballControls可用来通过旋转移动摄像头位置,实习整个场景的旋转和移动。DragControls包含两个事件:dragstart、dragend。

dragControls.addEventListener("dragstart", function(event){currentColor = event.object.material.color;event.object.material.color = new THREE.Color(0xffff00);event.object.material.transparent = true;event.object.material.opacity = 0.6;controls.enabled = false;});dragControls.addEventListener("dragend", function(event){event.object.material.opacity = 1.0;event.object.material.color = currentColor;controls.enabled = true;});

dragStart事件表示开始执行拖拽了,而dragend表示拖拽结束。可通过event.object获取当前拖拽的对象,然后就可以设置对象的属性了。这里需要特别注意的是,在拖拽开始时,我们需要禁止TrackballControls功能,才能够拖动物体。所以需要设置controls.enabled = false。当拖动结束,设置controls.enabled = true恢复TrackballControls的功能。

相关文章:

Matlab与线性代数 -- 矩阵的大小

本图文介绍了如何利用Matlab求矩阵的大小。

最近做了一个小小的系统,收获挺大的....我想总结一下

首先我要感谢老许,是他给了我这次机会.以后我会把我的经验一点一点总结出来....为那些在编程之路上的迷茫者找到方向活着让人兴奋...总觉的应该去做点什么做工程开发吧----我的老师阿温说过一句话让我记忆尤新:"坚持成就传奇".我想用他去勉力每一个在人生路上奋斗的人…

Hibernate和iBATIS 优缺点比较

选择Hibernate还是iBATIS都有它的道理&#xff1a;Hibernate的特点&#xff1a;Hibernate功能强大&#xff0c;数据库无关性好&#xff0c;O/R映射能力强&#xff0c;如果你对Hibernate相当精通&#xff0c;而且对Hibernate进行了适当的封装&#xff0c;那么你的项目整个持久层…

Matlab与线性代数 -- 矩阵的秩

本图文详细介绍了利用Matlab求矩阵秩的方法。

iOS开发 最近开发了蓝牙模块,在此记录总结一下

为什么80%的码农都做不了架构师&#xff1f;>>> 1.基本概念 <1>中心者模式&#xff1a;常用的&#xff08;其实99.99%&#xff09;就是使用中心者模式作为开发&#xff0c;就是我们手机作为主机&#xff0c;连接蓝牙外设。由于开发只用到了中心者模式&#x…

asp.net实现在网页上自动显示超链接以及Email地址

人们总喜欢在帖子中加上各种有用的URL链接或Email地址。而笔者当初设计时没有考虑到这一点&#xff0c;使得这些URL链接或Email地址只能以文字的形式而并不是以超链接的形式显示&#xff0c;其它浏览帖子的人还必须把这些URL链接拷贝到浏览器中或把Email地址拷贝到Outlook中才能…

用开放地址法中的线性探查法解决冲突实现哈希表的运算

为了更深的理解哈希算法&#xff0c;自己写了用开放地址法中的线性探查法解决冲突实现哈希表的运算。 /*** Created by lirui on 14-8-13.* 用开放地址法中的线性探查法解决冲突实现哈希表的运算。*/ public class MyHashSearch {public static final int SIZE 10;public sta…

Re: 求助:5道算法题

http://www.newsmth.net/frames.html发信人: cutepig (cutepig), 信区: Algorithm标 题: 求助&#xff1a;5道算法题发信站: 水木社区 (Sat Nov 10 18:25:06 2007), 站内1)given a integer, output its previous and next neighbor number which has the same number of bit 1…

Linux下des对称性加密

最近对接公安审计一些经历 对方的需求&#xff1a; 打成zip包对zip包进行des-cbc对称性加密&#xff0c;使用约定好的 -K和-iv值比如 -K "abcd$#!" -iv "efgh$#!"加密后做base64编码起初是想尝试用 php 去做&#xff0c;经过一阵折腾之后发现&#xff0c;p…

在软件中常用的“撤销”操作,其本质是“栈”!

本文介绍了栈的定义与操作并利用顺序表和链表实现了栈这种常用的数据结构。

用拉链法实现哈希算法的运算

package lirui.find;import java.util.LinkedList;/*** Created by lirui on 14-8-13.* 用拉链法实现哈希算法的运算*/ public class MyHashSearch2 {public static final int SIZE 10;public static MyHashElement[] hashtable new MyHashElement[SIZE];// 记录hash表中的数…

C#图片处理常见方法性能比较

在.NET编程中&#xff0c;由于GDI的出现&#xff0c;使得对于图像的处理功能大大增强。在文通过一个简单黑白处理实例介绍在.NET中常见的图片处理方法和原理并比较各种方法的性能。 黑白处理原理&#xff1a;彩色图像处理成黑白效果通常有3种算法&#xff1b; (1).最大值法: 使…

软件中常用的“发送邮件”、“打印文档”,其本质是“队列”!

本图文详细介绍了顺序队列、循环队列、链队列的实现过程。

二分查找的循环实现和递归实现

自己实现了二分查找的循环实现和递归实现 说明&#xff1a;二分查找适用于顺序存储结构&#xff0c;不适于链式存储结构&#xff0c;是一个高效的查找方法。虽然折半查找效率高&#xff0c;但是要排序&#xff0c;排序本身是一种很费时的运算。要求传入的表是有序的。二分查找的…

CentOS6.8 编译安装LNMP

思路&#xff1a;根据Linux系统以及公司网站系统的信息&#xff0c;选择合适的安装包进行安装 一、查看系统信息 # uname -a # 查看内核/操作系统/CPU信息 # head -n 1 /etc/issue # 查看操作系统版本 # grep MemTotal /proc/meminfo # 查看内…

js入门·循环与判断/利用函数的简单实例/使用对象/列举对象属性的名称

1,列举对象属性的名称<script language"javascript">varobjnewObject();obj.a"您好&#xff0c;我是田洪川";obj.b"你是田洪川咋的&#xff0c;不得了啊&#xff1f;";obj.c"西西&#xff0c;哈哈&#xff0c;我是属性 c ";//上…

Matlab与数据结构 -- 对向量的排序

本图文介绍了Matlab怎样实现对向量的排序。

HashMap和HashSet原理及底层实现

HashMap底层用哈希算法实现&#xff0c;下面看一下哈希算表的整体概括&#xff1a; 当map.put(“key”,”values”);的时候&#xff0c;底层是这样的&#xff1a; static final Entry<?,?>[] EMPTY_TABLE {}; transient Entry<K,V>[] table (Entry<K,V&g…

Study on Android【三】--Intent消息传递

在前面写Android的ContentProvider时候&#xff0c;可以看到那是基于观察者模式的一个消息传递方法。每一个Cursor、ContentResolver做为一个小的注册中心&#xff0c;相关观察者可以在这个中心注册&#xff0c;更新消息由注册中心分发给各个观察者。而在MFC或Winform中&#x…

Matlab与数据结构 -- 对矩阵的排序

本图文介绍了Matlab怎样实现对矩阵的排序。

Java_中快速获取系统时间

直接调用System的currentTimeMillis()即可&#xff01; long start System.currentTimeMillis(); System.out.println("Start time : "start);

servlet必知细节(一)

servlet必知细节&#xff08;一&#xff09; 今天复习了一下servlet&#xff0c;有过一些编程经验后&#xff0c;与最初学习servlet相比&#xff0c;对servlet理解的角度不同了&#xff0c;最初只是学习了如何写一个servlet&#xff0c;api怎么用&#xff0c;现在从更深处了解了…

办公室“暧昧”的几种结局。

暧昧结局一&#xff1a;以消散结尾 外贸公司职员小雨 p: [( I J( n, i/ L( L 那是我刚来这家公司的时候&#xff0c;参加面试时我就关注到其中一个面试我的男人长得不错&#xff0c;言谈举止也都很儒雅&#xff0c;所以第一面就留下了深刻印象。等到我被录取正式上班后…

Matlab与机器学习-- 数据的归一化

本文介绍了Matlab对数据归一化的处理函数mapminmax。

ASP.NET中的母版页

ASP.NET中的母版页 添加一个"母版页",使用<asp:ContentPlaceHolder>挖坑,新建的母版页已经自动设置了两个ContentPlaceHolder创建使用母版页的具体页面,WebSite是新建"Web窗体"的时候勾选"选择模板页",WebApplication是新建"Web内容窗…

servlet必知细节(二)--servlet执行过程

servlet必知细节(二)--servlet执行过程 我们知道&#xff0c;servlet没有main函数&#xff0c;那么&#xff0c;servlet是怎么调用的呢&#xff1f;实际上&#xff0c;servlet 是由tomcat调用的&#xff0c;tomcat调用servlet程序执行。由调用栈可以看到&#xff0c;当一个请求…

workerman源码分析之启动过程

2019独角兽企业重金招聘Python工程师标准>>> http://www.cnblogs.com/CpNice/p/4714182.html 转载于:https://my.oschina.net/yonghan/blog/898076

MD5加密方法

/**//// <summary>/// 16位MD5加密方法/// </summary>/// <param name"str">原文</param>/// <returns>密文</returns>publicstaticstringgetMd5(stringstr){ MD5CryptoServiceProvider md5 new MD5CryptoServiceProvider()…

如何教计算机认识手写数字(上)

本图文介绍了一种简单的教会计算机识别手写数字的方法。

servlet必知细节(三)-- DefaultServlet

servlet必知细节&#xff08;三&#xff09;-- DefaultServlet 缺省servlet&#xff1a;org.apache.catalina.servlets.DefaultServlet&#xff0c;作用是处理其他servlet处理不到的请求 我们知道&#xff0c;在我们工程的web.xml中&#xff0c;会配置servlet映射&#xff0c…