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使用的是右手 ...
随机推荐
- 深入浅出 - Android系统移植与平台开发(七)- 初识HAL
作者:唐老师,华清远见嵌入式学院讲师. 1. HAL的module与stub HAL(Hardware AbstractLayer)硬件抽象层是Google开发的Android系统里上层应用对底层硬件 ...
- Java_JAVA6动态编译的问题
摘自:http://www.iteye.com/problems/14909 在使用JAVA6动态编译时遇到的一个问题,动态编译方法已经写就.通过main方法调用的动态编译时,编译通过,并可以使用编译 ...
- DataContract
DataContractAttribute Class is in the System.Runtime.Serialization namespace. But you should add ref ...
- 李洪强iOS经典面试题126
1.#import和#include的区别,@class代表什么? @class一般用于头文件中需要声明该类的某个实例变量的时候用到,在m文件中还是需要使用#import 而#import比起#inc ...
- iOS开发中几个重要的方法
iOS开发中几个重要的方法: 加载类到内存,程序刚启动的时候调用,调用在main函数之前 1.+(void)load{ } 初始化类,类第一次使用的时候调用一次 2.+(void)initialize ...
- js打印功能
1.window.print方式: jsp页面 打印按钮: <input type="button" value="打印" onclick=& ...
- Centos Odoo Service Config
#!/bin/sh ### BEGIN INIT INFO # Provides: openerp-server # Required-Start: $remote_fs $syslog # Requ ...
- Hack技术
Hack技术 1.IE条件注释法,微软官方推荐的hack方式. 只在IE下生效 <!--[if IE]> <link rel="stylesheet" href= ...
- Web前端代码页面布局总结
一. html (1)编码:所有编码均采用xhtml,标签必须闭合,属性值用双引号包括,编码统一为utf-8. (2)语义化:语义化html,正确使用标签. (3)文件命名:命名以中文命名,依实际模 ...
- service mysql start出错,mysql启动不了,解决mysql: unrecognized service错误
service mysql start出错,mysql启动不了,解决mysql: unrecognized service错误的方法如下: [root@ctohome.com ~]# service ...