Web3D编程总结——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(2).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 != 0 || this.speeda != 0||this.speedc!=0)
{//如果物体在某个方向有速度
var vector4=new THREE.Vector3(0,this.speedc*1000,0);
var vector3=(this.v1.clone().multiplyScalar(this.speeda*1000)).add(this.v0.clone().multiplyScalar(this.speedw*1000)).add(vector4);
var vector5=vector3.clone().normalize().multiplyScalar(this.size);
this.vector3=vector3.clone().add(vector5);
this.updateLine2(this.line2.uuid,new THREE.Vector3(0,0,0),this.vector3,0x00ff00);//从物体质心向物体运动方向,做一条长度和速度成正比的线段
//line2是planetGroup的子元素,它本身就会和this.object3D.position一起移动,如果再加上一个this.object3D.position就重复了,
//这个也是某种意义上的“相对运动”
this.testCollision();//碰撞检测
//检测无误后,最终确定下一帧位置
if(this.flag_coll==0) //没有发生碰撞
{
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=0;
this.speedc=0;
this.speedw=0;
this.flag_coll=0;//重置为没有发生碰撞的状态
currentlyPressedKeys[65]=false;
currentlyPressedKeys[68]=false;
currentlyPressedKeys[87]=false;
currentlyPressedKeys[83]=false;
currentlyPressedKeys[32]=false;
currentlyPressedKeys[17]=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 > 0 ) {
var flag_safe=0;//应该可以省掉这个变量
//安然走完下面的循环说明,在碰撞区内没有任何其他物体
for(var i=0;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(0,0,0),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 > 0 )
{
flag_safe=1;
for(var j=0;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=0;//没有发生碰撞
}
break;
}
}
if(flag_safe==1)
{
this.flag_coll = 1;
}
}
这里的逻辑还不够优雅,下次再调整吧
四、优化方向:
1、现在的“3D碰撞检测”实现了最简单情况下的碰撞检测,但算法仍具有很大的局限性,比如这种情况下,碰撞检测射线永远无法穿过其他物体:
对此我想到的解决方法是:
将raycaster扩充为检测方向上的多条平行射线来检测物体边缘碰撞的情况,而这需要用到线性代数的相关知识,需再复习一下。
2、在没有设置半透明的情况下运行,可能会发生物体重叠但没有判断碰撞的情况,怀疑是因为我采用的是“先碰撞后检测”的方法,Three.js认为重叠部分的图元不需要绘制将其自动舍弃,导致射线检测不到重叠部分。
五、扩展:
昨天发现美国前辈Lee Stemkoski的Three.js示例展示了另一种碰撞检测方法,和我的方法相比各有优缺点
演示地址:http://stemkoski.github.io/Three.js/Collision-Detection.html
核心代码:
for (var vertexIndex = 0; 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 > 0 && collisionResults[0].distance < directionVector.length() )
appendText(" Hit ");
}
该方法从物体的中心向物体的每一个顶点做一条碰撞检测射线,如果碰到第一个物体的碰撞点到物体中心的距离小于物体中心到该顶点的距离,则认为发生碰撞。
Web3D编程总结——3D碰撞检测初探的更多相关文章
- Web三维编程入门总结之三:3D碰撞检测初探
自己动手写一个方法比分析他人的写的方法困难很多,由此而来的对程序的进一步理解也是分析别人的代码很难得到的. 一.先来几张效果图: 1.场景中有两个半径为1的球体,蓝色线段从球心出发指向球体的“正向” ...
- Web3D编程入门总结——面向对象的基础Web3D框架
本篇主要通过分析Tony Parisi的sim.js库(原版代码托管于:https://github.com/tparisi/WebGLBook/tree/master/sim),总结基础Web3D框 ...
- Web3D编程入门总结——WebGL与Three.js基础介绍
/*在这里对这段时间学习的3D编程知识做个总结,以备再次出发.计划分成“webgl与three.js基础介绍”.“面向对象的基础3D场景框架编写”.“模型导入与简单3D游戏编写”三个部分,其他零散知识 ...
- 【Qt编程】3D迷宫游戏
说起迷宫想必大家都很熟悉,个人感觉迷宫对人的方向感是很大的考验,至少我的方向感是不好的,尤其是在三维空间中.由于这段时间帮导师做项目用到了三维作图,便心血来潮想做个三维迷宫玩玩.要想画出三维的迷宫游戏 ...
- QT Graphics-View 3D编程例子- 3D Model Viewer
学习在Graphics-View框架中使用opengl进行3D编程,在网上找了一个不错的例子“3D Model Viewer”,很值得学习. 可以在http://www.oyonale.com/acc ...
- 【Unity】用Shader编程实现3D红心
有些形状,即使没有3D美术设计师提供模型,也能够用代码生成. 对于想保持原创性不想借用他人模型的独立开发人员来说,这无非是一个非常重要的途径. 今天献给大家的是用Shader编程实现的一颗红心,寄托下 ...
- 3D碰撞检测
为了确保任何区域的空间不被多于1个物体占用,我们需要基于物体间的空间信息来做碰撞检测. 碰撞检测中重要的事情是有大量的测试,因此需要理由GPU资源. 例如:如果我们有n个物体,一个物体将会碰撞n-1个 ...
- 基于 HTML5 Canvas 的 3D 碰撞检测
这是公司大神写的一个放官网上给用户学习的例子,我一开始真的不知道这是在干嘛,就只是将三个形状图元组合在一起,然后可以同时旋转.放大缩小这个三个图形,点击"Animate"就能让中间 ...
- 使用three.js开发3d地图初探
three是图形引擎,而web二维三维地图都是基于图形引擎的,所以拿three来开发需求简单的三维地图应用是没什么问题的. 1.坐标转换 实际地理坐标为经度.纬度.高度,而three.js使用的是右手 ...
随机推荐
- 【wikioi】2822 爱在心中
题目链接 算法:Tarjan+dfs(最短路的都行,判连通而已) 先了解一下什么是Tarjan Tarjan算法用于求出图中所有的强连通分量. 转自NOCOW:点击打开链接 ============= ...
- 最全的iOS面试题及答案-转载
1. Object-c的类可以多重继承么?可以实现多个接口么?Category是什么?重写一个类的方式用继承好还是分类好?为什么? 答: Object-c的类不可以多重继承:可以实现多个接口,通过实现 ...
- 关于C#引用Dll后,找不到命名空间的问题
在引用里明确添加了一个Dll,能够看到该Dll详细信息,可就是用using找不到命名空间.并且发现刚引用时是有该命名空间,一编译就消失了. 最后发现原因如下: 原目标框架为.Net Framework ...
- Eclipse IDE for C/C++ Developers安装配置详解
Eclipse IDE for C/C++ Developers安装配置详解(转) 转自:http://hi.baidu.com/ltb6w/item/986532efd712460f570f1ddc ...
- C#关于控件的上下左右移动
C#怎么让控件上下左右移动?(转) http://wenwen.sogou.com/z/q231436494.htm 在winform中捕获上下左右键等控制键的按键事件(转) http://blog. ...
- js正则表达式 验证手机号,email地址和邮政编码
手机号码的验证(13开头和158,159开头,共11位) var re; var ss=document.getElementById('textbox3').value; ...
- vim operation
note: 转自 www.quora.com ,很好的网站. 具体链接如下: https://www.quora.com/What-are-some-impressive-demos-of-Vim- ...
- Button,CheckBox,Lable,RadioButton,ComboBox,TextBox六个简单控件的使用
所有文字的更改全部在Text属性中更改! ComboBox:点击右上方小箭头,选择编辑项弹出: RadioButton:,Checked属性选择True,表示已被选中: Button:在设计中双击按钮 ...
- Java 多继承
浅浅地上了几天 Java 的课程,然后怎么也想不明白:Java 不支持多继承有和接口到底是什么样的关系? 一只牛的故事. // 叫的抽象基类 abstract class call{ } // 跑的抽 ...
- MySQL数据库基本指令(全)
数据库基本类型 CHAR 固定长度字符串 char(10) 存两位 占10位 查询有优势VARCHAR 可变长度字符串 varchar(10) 存两位 占两位 存储有优势 枚举类型 只能取一个set类 ...