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

一、先来几张效果图:

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. [UWP]抄抄《CSS 故障艺术》的动画

    1. 前言 什么是故障艺术(Glitch Art 风)?我们熟知的抖音的 LOGO 正是故障艺术其中一种表现形式.它有一种魔幻的感觉,看起来具有闪烁.震动的效果,很吸引人眼球.故障艺术它模拟了画面信号 ...

  2. x86汇编指令集大全(带注释)

    X86和X87汇编指令大全(有注释) PUSH 压栈.POP 来说是出栈.入栈(push):---------- 一.数据传输指令 ---------------------------------- ...

  3. asp.net core系列 76 Apollo 快速安装模式下填坑和ASP.NetCore结合使用

    前言:由于公司占时没有运维,出于微服务的需要,Apollo只能先装在windows 阿里云上跑起来,由于环境及网络等问题,在安装过程中遇到很多坑,算是一个个坑填完后,最终实现. 一. java jdk ...

  4. ajax结合sweetalert弹出框删除数据

    思路:

  5. Spring Controller单例与线程安全那些事儿

    目录 单例(siingleton)作用域 原型(Prototype)作用域 多个HTTP请求在Spring控制器内部串行还是并行执行方法? 实现单例模式并模拟大量并发请求,验证线程安全 附录:Spri ...

  6. 【笔记3-31】Python语言基础-序列sequence

    序列sequence 可变序列 列表 list 字典 不可变序列 字符串 str 元祖 tuple 通过索引修改列表 del 删除元素 del my_list[2] 切片赋值只能是序列 .insert ...

  7. Redis 缓存更新一致性

    当执行写操作后,需要保证从缓存读取到的数据与数据库中持久化的数据是一致的,因此需要对缓存进行更新. 因为涉及到数据库和缓存两步操作,难以保证更新的原子性. 在设计更新策略时,我们需要考虑多个方面的问题 ...

  8. Prism+MaterialDesign+EntityFramework Core+Postgresql WPF开发总结 之 基础篇

    本着每天记录一点成长一点的原则,打算将目前完成的一个WPF项目相关的技术分享出来,供团队学习与总结. 总共分三个部分: 基础篇主要争对C#初学者,巩固C#常用知识点: 中级篇主要争对WPF布局与美化, ...

  9. (note)从小白到产品经理之路

    学习了云课堂的产品课程,整理出部分笔记,以作备用参考,方便实际运用过程中查看巩固. 1.产品工具:Axure.mindmanager.viso.办公软件wps 2.产品人需要具备的品格 富有同理心,习 ...

  10. Prism 源码解读6-事件聚合

    0 介绍 事件提供的是1对多的绑定,通过委托链实现对订阅者的调用,事件必须要通过发布者调用.同时事件订阅是强引用,事件订阅者的生命周期总是大于等于事件发布者.如果代码中事件很多就会充斥着各种事件的订阅 ...