自己动手写一个方法比分析他人的写的方法困难很多,由此而来的对程序的进一步理解也是分析别人的代码很难得到的。

一、先来几张效果图:

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碰撞检测初探的更多相关文章

  1. Web三维编程入门总结之二:面向对象的基础Web3D框架

    本篇主要通过分析Tony Parisi的sim.js库(原版代码托管于:https://github.com/tparisi/WebGLBook/tree/master/sim),总结基础Web3D框 ...

  2. Web三维编程入门总结之一:WebGL与Threejs入门知识

    /*在这里对这段时间学习的3D编程知识做个总结,以备再次出发.计划分成“webgl与three.js基础介绍”.“面向对象的基础3D场景框架编写”.“模型导入与简单3D游戏编写”三个部分,其他零散知识 ...

  3. threejs构建web三维视图入门教程

    本文是一篇简单的webGL+threejs构建web三维视图的入门教程,你可以了解到利用threejs创建简单的三维图形,并且控制图形运动.若有不足,欢迎指出. 本文使用的框架是three.js gi ...

  4. Web3D编程总结——3D碰撞检测初探

    自己动手写一个方法比分析他人的写的方法困难很多,由此而来的对程序的进一步理解也是分析别人的代码很难得到的. 一.先来几张效果图: 1.场景中有两个半径为1的球体,蓝色线段从球心出发指向球体的“正向” ...

  5. cesium编程入门(六)添加 3D Tiles,并调整位置,贴地

    添加 3D Tiles,并调整位置 3D Tiles 是什么 3DTiles数据集是cesium小组AnalyticlGraphics与2016年3月定义的一种数据集,3DTiles数据集以分块.分级 ...

  6. VS2010/MFC编程入门之三(VS2010应用程序工程中文件的组成结构)

    VS2010/MFC编程入门之三(VS2010应用程序工程中文件的组成结构)-软件开发-鸡啄米 http://www.jizhuomi.com/software/143.html   鸡啄米在上一讲中 ...

  7. 《Web编程入门经典》

    在我还不知道网页的基础结构的时候,我找过很多本介绍Web基础的书籍,其中这本<Web编程入门经典>,我认为是最好的. 这本书内容很全面.逻辑很严谨.结构很清晰.语言文字浅显易懂. 看这本书 ...

  8. cesium编程入门(七)3D Tiles,模型旋转

    cesium编程入门(七)3D Tiles,模型旋转 上一节介绍了3D Tiles模型的位置移动,和贴地的操作,这一节来聊一聊模型的旋转, 参考<WebGl编程指南>的第四章 假设在X轴和 ...

  9. VS2010/MFC编程入门之三十八(状态栏的使用详解)

    上一节中鸡啄米讲了工具栏的创建.停靠与使用,本节来讲解状态栏的知识. 状态栏简介 状态栏相信大家在很多窗口中都能见到,它总是用来显示各种状态.状态栏实际上也是一个窗口,一般分为几个窗格,每个窗格分别用 ...

随机推荐

  1. Centos7配置IP地址和DNS

    目录 一.查看IP地址 1.ifconfig命令 2.ip addr命令 二.配置网卡 1.动态IP地址 2.静态IP地址 3.重启网卡 三.注意事项 四.本地虚拟机设置静态IP后不能上网的解决方法 ...

  2. Unity 游戏框架搭建 2019 (九~十二) 第一章小结&第二章简介&第八个示例

    第一章小结 为了强化教程的重点,会在合适的时候进行总结与快速复习. 第二章 简介 在第一章我们做了知识库的准备,从而让我们更高效地收集示例. 在第二章,我们就用准备好的导出工具试着收集几个示例,这些示 ...

  3. 还是只使用console.log()进行调试?好吧,其实还有更多。

    在浏览器控制台中打印消息无疑可以拯救所有开发人员. console.log()消息就像您的大多数疾病的药,同时调试了代码中的一些有线问题. 那里的大多数开发人员都喜欢— 让我们在浏览器中打印消息以了解 ...

  4. JAVA开发中如何优化类的设计

    具体类依赖于抽象类,而非抽象类依赖于具体类.这样做有利于一个抽象类扩展多个具体类. 开放封闭原则:对扩展开放,对修改封闭. 1.永远保持数据私有 保持数据的私有是设计类时,必须重点考虑的问题.保持私有 ...

  5. JSP(三)----EL表达式

    ##  EL表达式 1.概念:Expression  alnguage  表达式语言 2.作用:替换和简化JSP页面中java代码的编写 3.语法:${表达式} 4.注意: *  jsp默认支持EL表 ...

  6. abp(net core)+easyui+efcore实现仓储管理系统——入库管理之六(四十二)

    abp(net core)+easyui+efcore实现仓储管理系统目录 abp(net core)+easyui+efcore实现仓储管理系统——ABP总体介绍(一) abp(net core)+ ...

  7. linux bash吧,还有啥Bourne Again Shell

    linux bash吧,还有啥Bourne Again Shell bash吧,还有啥Bourne Again Shell 头部要写#!/bin/bash set -x #open script de ...

  8. CF 631C report

    Each month Blake gets the report containing main economic indicators of the company "Blake Tech ...

  9. PyTorch Hub发布!一行代码调用最潮模型,图灵奖得主强推

    为了调用各种经典机器学习模型,今后你不必重复造轮子了. 刚刚,Facebook宣布推出PyTorch Hub,一个包含计算机视觉.自然语言处理领域的诸多经典模型的聚合中心,让你调用起来更方便. 有多方 ...

  10. Tarjan算法(模板)

    算法思想: 首先要明确强连通图的概念,一个有向图中,任意两个点互相可以到达:什么是强连通分量?有向图的极大连通子图叫强连通分量. 给一个有向图,我们用Tarjan算法把这个图的子图(在这个子图内,任意 ...