Web三维编程入门总结之三:3D碰撞检测初探
自己动手写一个方法比分析他人的写的方法困难很多,由此而来的对程序的进一步理解也是分析别人的代码很难得到的。
一、先来几张效果图:
1、场景中有两个半径为1的球体,蓝色线段从球心出发指向球体的“正向”
2、物体被选中后改变纹理图片和透明度,可以使用“w、s、a、d、空格、ctrl”控制物体相对于物体的正向“前、后、左、右、上、下”移动,按住按键时间越长移动速度越快,绿色线段由球心指向物体运动方向,速度越快露出物体表面的部分越长,按“g”停止所有移动,再次点击物体取消选中状态。
3、可以选取多个物体同时移动。
4、两物体发生碰撞后停止移动,红色线段由物体球心指向运动方向上遇到的第一个其他物体
二、碰撞检测原理:
借用THREE.Raycaster来进行碰撞检测,因为Raycaster不能检测到物体的“内表面”,所以使用反射法。
三、程序实现:
完整程序代码可以在http://files.cnblogs.com/files/ljzc002/App2.zip下载查看,其中包括详细注释,这里解释一下几个比较重要的段落。
1、绘制示意物体运动情况的线段
this.line1;//自有方向线
this.line2;//运动方向线
this.line3;//碰撞检测线
var vector2=this.v0.clone().multiplyScalar().add(this.object3D.position);//通过向量算出线段的结束点
this.line1=this.createLine2(this.object3D.position,vector2,0x0000ff,this.planetGroup,"line1");//物体在水平方向的朝向线
this.line2=this.createLine2(this.object3D.position,this.object3D.position,0x00ff00,this.planetGroup,"line2");
this.line3=this.createLine2(this.object3D.position,this.object3D.position,0xff0000,this.planetGroup,"line3");
其中点vector2由向量v0乘以2加上this.object3D.position得到,作为直线line1的结束点。注意v0后的“.clone()”如果去掉则v0本身也会应用这些变化,最终变得与vector2相同。
line2和line3被初始化为一个点。
这里建立的“line”对象并没有碰撞检测功能,纯粹是用来看的。
if (this.speedw != || this.speeda != ||this.speedc!=)
{//如果物体在某个方向有速度
var vector4=new THREE.Vector3(,this.speedc*,);
var vector3=(this.v1.clone().multiplyScalar(this.speeda*)).add(this.v0.clone().multiplyScalar(this.speedw*)).add(vector4);
var vector5=vector3.clone().normalize().multiplyScalar(this.size);
this.vector3=vector3.clone().add(vector5);
this.updateLine2(this.line2.uuid,new THREE.Vector3(,,),this.vector3,0x00ff00);//从物体质心向物体运动方向,做一条长度和速度成正比的线段
//line2是planetGroup的子元素,它本身就会和this.object3D.position一起移动,如果再加上一个this.object3D.position就重复了,
//这个也是某种意义上的“相对运动”
this.testCollision();//碰撞检测
//检测无误后,最终确定下一帧位置
if(this.flag_coll==) //没有发生碰撞
{
this.object3D.position.add(this.v0.clone().multiplyScalar(this.speedw));
this.object3D.position.add(this.v1.clone().multiplyScalar(this.speeda));
this.object3D.position.y += this.speedc;//直接使用等号会设置失败!!
}
else
{
this.speeda=;
this.speedc=;
this.speedw=;
this.flag_coll=;//重置为没有发生碰撞的状态
currentlyPressedKeys[]=false;
currentlyPressedKeys[]=false;
currentlyPressedKeys[]=false;
currentlyPressedKeys[]=false;
currentlyPressedKeys[]=false;
currentlyPressedKeys[]=false;
}
}
根据物体的运动情况更新line2的方向和长度。在这里vector3由物体在各个方向上的速度分量组合而成,vector5负责把vector3调整为适合显示的长度,this.vector3和vector3是不同的对象,表示line2一个端点的位移。
updateLine2更新line2的端点,表现出速度的变化。我们可以看到它的两个端点位置参数是(0,0,0)和一个向量而不是前面图中的球心位置和“球心位置加向量得到的点”,这体现出了Three.js父子物体间的相对性。(简单的碰撞示例原本不需要使用子物体,我是不是自讨苦吃?)
该示例延续自上一篇中的太阳系模型,物体间的关系如下图:
planetOrbitGroup和planetGroup是“Object3D”对象,这种“物体”没有大小、颜色属性只有位置和姿态属性(默认位于父物体的原点),“Mesh”和“Line”多重继承于Object3D具有顶点(geometry)、纹理(material)属性。
planetOrbitGroup是Scene的子物体位于世界坐标系原点,planetGroup是planetOrbitGroup的子物体强制定位于世界坐标系x=3处,globeMesh和line2是planetGroup的子物体默认位于planetGroup的原点。
当planetOrbitGroup移动时(改变this.object3D.position),这个移动效果会被它所有的后代对象继承,所以我们把line2的一个顶点设为(0,0,0)后会自动继承它所有祖先元素的移动效果(this.object3D.position+(3,0,0))。
2、基于射线的碰撞测试
var raycaster= new THREE.Raycaster(this.planetGroup.position.clone().add(this.object3D.position),this.vector3.clone().normalize());//从物体中心向实际移动方向发出一条射线
raycaster.far=this.object3D.inter_length;//射线“长度”
var intersects = raycaster.intersectObjects(scene.children,true);
建立第一条射线,与前面的线段不同,这里的射线是数学意义上的射线,它不是一个物体也无法被渲染出来。因为它不是任何物体的子物体,所以raycaster的端点位置取世界坐标而非相对坐标,注意和前面同样位置的线段端点的不同。
因为我们使用了子物体,所以intersectObjects的第二个参数必须设为true以强制检查每一个子物体,否则raycaster只会检查第一个参数这一层的物体而忽略掉globeMesh。
if ( intersects.length > ) {
var flag_safe=;//应该可以省掉这个变量
//安然走完下面的循环说明,在碰撞区内没有任何其他物体
for(var i=;i<intersects.length;i++)
{
//规定碰撞的物体必须是可见的,必须是有面的,必须不是原物体,必须在碰撞检测范围以内(其实是因为射线穿过子物体结果的不确定性)
if (intersects[i].object.visible && intersects[i].face&&(this.object3D.inter_group!=intersects[i].object.inter_group)&&intersects[i].distance<this.object3D.inter_length) { var intersected = intersects[i];
this.updateLine2(this.line3.uuid,new THREE.Vector3(,,),intersected.point.clone().sub(this.planetGroup.position.clone().add(this.object3D.position)),0xff0000);//碰撞检测线
前面提到raycaster无法检测到物体的内表面,但在涉及到子物体检测时,这一命题变得不确定了,所以要加上更多的判断条件(这是不是自己坑自己。。。)
var raycaster2= new THREE.Raycaster(intersected.point,this.vector3.clone().negate().normalize());//射线与物体相交后反射回来
raycaster2.far=this.object3D.inter_length;
var intersects2 = raycaster2.intersectObjects(scene.children,true);
//既然已经碰到了别的物体,就必须反射回原物体,才能保证不碰撞(反射回原物体另一面的情况由碰撞边界值剔除)
if ( intersects2.length > )
{
flag_safe=;
for(var j=;j<intersects2.length;j++)
{
if (intersects2[j].object.visible && intersects2[j].face&&(intersected.object.inter_group!=intersects2[j].object.inter_group)&&intersects2[j].distance<this.object3D.inter_length)
{
if(intersects2[j].object.inter_group==this.object3D.inter_group)//inter_group属性相同,返回了原物体
{
flag_safe=;//没有发生碰撞
}
break;
}
}
if(flag_safe==)
{
this.flag_coll = ;
}
}
这里的逻辑还不够优雅,下次再调整吧
四、优化方向:
1、现在的“3D碰撞检测”实现了最简单情况下的碰撞检测,但算法仍具有很大的局限性,比如这种情况下,碰撞检测射线永远无法穿过其他物体:
对此我想到的解决方法是:
将raycaster扩充为检测方向上的多条平行射线来检测物体边缘碰撞的情况,而这需要用到线性代数的相关知识,需再复习一下。
2、在没有设置半透明的情况下运行,可能会发生物体重叠但没有判断碰撞的情况,怀疑是因为我采用的是“先碰撞后检测”的方法,Three.js认为重叠部分的图元不需要绘制将其自动舍弃,导致射线检测不到重叠部分。
五、扩展:
昨天发现美国前辈Lee Stemkoski的Three.js示例展示了另一种碰撞检测方法,和我的方法相比各有优缺点
演示地址:http://stemkoski.github.io/Three.js/Collision-Detection.html
核心代码:
for (var vertexIndex = ; vertexIndex < MovingCube.geometry.vertices.length; vertexIndex++)
{
var localVertex = MovingCube.geometry.vertices[vertexIndex].clone();
var globalVertex = localVertex.applyMatrix4( MovingCube.matrix );
var directionVector = globalVertex.sub( MovingCube.position ); var ray = new THREE.Raycaster( originPoint, directionVector.clone().normalize() );
var collisionResults = ray.intersectObjects( collidableMeshList );
if ( collisionResults.length > && collisionResults[].distance < directionVector.length() )
appendText(" Hit ");
}
该方法从物体的中心向物体的每一个顶点做一条碰撞检测射线,如果碰到第一个物体的碰撞点到物体中心的距离小于物体中心到该顶点的距离,则认为发生碰撞。
Web三维编程入门总结之三:3D碰撞检测初探的更多相关文章
- Web三维编程入门总结之二:面向对象的基础Web3D框架
本篇主要通过分析Tony Parisi的sim.js库(原版代码托管于:https://github.com/tparisi/WebGLBook/tree/master/sim),总结基础Web3D框 ...
- Web三维编程入门总结之一:WebGL与Threejs入门知识
/*在这里对这段时间学习的3D编程知识做个总结,以备再次出发.计划分成“webgl与three.js基础介绍”.“面向对象的基础3D场景框架编写”.“模型导入与简单3D游戏编写”三个部分,其他零散知识 ...
- threejs构建web三维视图入门教程
本文是一篇简单的webGL+threejs构建web三维视图的入门教程,你可以了解到利用threejs创建简单的三维图形,并且控制图形运动.若有不足,欢迎指出. 本文使用的框架是three.js gi ...
- Web3D编程总结——3D碰撞检测初探
自己动手写一个方法比分析他人的写的方法困难很多,由此而来的对程序的进一步理解也是分析别人的代码很难得到的. 一.先来几张效果图: 1.场景中有两个半径为1的球体,蓝色线段从球心出发指向球体的“正向” ...
- cesium编程入门(六)添加 3D Tiles,并调整位置,贴地
添加 3D Tiles,并调整位置 3D Tiles 是什么 3DTiles数据集是cesium小组AnalyticlGraphics与2016年3月定义的一种数据集,3DTiles数据集以分块.分级 ...
- VS2010/MFC编程入门之三(VS2010应用程序工程中文件的组成结构)
VS2010/MFC编程入门之三(VS2010应用程序工程中文件的组成结构)-软件开发-鸡啄米 http://www.jizhuomi.com/software/143.html 鸡啄米在上一讲中 ...
- 《Web编程入门经典》
在我还不知道网页的基础结构的时候,我找过很多本介绍Web基础的书籍,其中这本<Web编程入门经典>,我认为是最好的. 这本书内容很全面.逻辑很严谨.结构很清晰.语言文字浅显易懂. 看这本书 ...
- cesium编程入门(七)3D Tiles,模型旋转
cesium编程入门(七)3D Tiles,模型旋转 上一节介绍了3D Tiles模型的位置移动,和贴地的操作,这一节来聊一聊模型的旋转, 参考<WebGl编程指南>的第四章 假设在X轴和 ...
- VS2010/MFC编程入门之三十八(状态栏的使用详解)
上一节中鸡啄米讲了工具栏的创建.停靠与使用,本节来讲解状态栏的知识. 状态栏简介 状态栏相信大家在很多窗口中都能见到,它总是用来显示各种状态.状态栏实际上也是一个窗口,一般分为几个窗格,每个窗格分别用 ...
随机推荐
- 记一次RSA解密过程
有问题可以评论 openssl rsa -pubin -text -modulus -in warmup -in pub.key
- GPP(Group Policy Preference)组策略偏好漏洞利用
总结与反思: GPP中管理员给域成员添加的账号信息存在xml,可以直接破解拿到账号密码. Windows Sever 2008 的组策略选项(GPP)是一个新引入的插件,方便管理员管理的同时也引入了安 ...
- Centos 非root用户安装rabbitmq
RabbitMQ是一个开源消息队列服务器,您可以使用它来构建您的消息传递应用程序.简单地说,您可以将来自一个应用程序的消息放入队列,并从来自相同应用程序或不同应用程序的队列中检索消息.您可以使用多种编 ...
- hdu1253胜利大逃亡(城堡的怪物太狠,主角难免天天逃亡)
题目链接:http://icpc.njust.edu.cn/Problem/Hdu/1253/ 其实就是二维扩展到三维了,增加了搜索方向,其他的没什么不同. 代码如下: #include<bit ...
- Contest 161
2019-11-03 20:35:18 总体感受:本周的赛题完全是反过来的,第一题最难,第二题次之,最后的hard反而是最简单的. 注意点:心态放平稳,慢慢来.
- JavaScript JSON 与 AJAX
JavaScript JSON 与 AJAX JSON 是一种轻量的数据交互格式,与 AJAX 配合完成前端页面与服务端的信息传递,本文介绍 JSON 的使用.原生 AJAX 写法.JSONP 跨域解 ...
- coding++:kafka问题:zookeeper is not a recognized option zookeeper参数不支持
– zookeeper is not a recognized option主要原因是 Kafka 版本过高,命令不存在. 使用新版本: ./bin/kafka-console-consumer.sh ...
- Selenium系列(二十) - PageObject模式的详细介绍
如果你还想从头学起Selenium,可以看看这个系列的文章哦! https://www.cnblogs.com/poloyy/category/1680176.html 其次,如果你不懂前端基础知识, ...
- 模块 heapq_堆排序
_heapq_堆排序 该模块提供了堆排序算法的实现.堆是二叉树,最大堆中父节点大于或等于两个子节点,最小堆父节点小于或等于两个子节点. 创建堆 heapq有两种方式创建堆, 一种是使用一个空列表,然后 ...
- 【WPF学习】第六十四章 构建基本的用户控件
创建一个简单用户控件是开始自定义控件的好方法.本章主要介绍创建一个基本的颜色拾取器.接下来分析如何将这个控件分解成功能更强大的基于模板的控件. 创建基本的颜色拾取器很容易.然而,创建自定义颜色拾取器仍 ...