three.js八叉树Octree扩展库介绍

参考资料:threejs中文网

threejs qq交流群:814702116

three.js八叉树Octree扩展库介绍

本章节学习八叉树Octree目的,是为了实现漫游的碰撞检测功能,比如遇到装障碍物被挡住、比如爬坡和上楼梯。

你可以打开本节课的工厂漫游案例体验测试。

本节课内容会比较多,不过大部分只是作为了解和扩展学习,你只需要你掌握下面3行代码即可。

// 引入八叉树扩展库
import { Octree } from 'three/examples/jsm/math/Octree.js';
const worldOctree = new Octree();
// 分割模型,生成八叉树节点
worldOctree.fromGraphNode(模型对象);

八叉树基本原理解释

下面给大家简单介绍下八叉树概念,初学者不要求记住具体细节,先有个印象就行。

通过前面基础内容2.3. 网格模型(三角形概念)的学习,大家都知道网格模型Mesh本质是由三角形构成,三角形由顶点构成,这些三角形和自己的顶点数据分布在3D空间中。

const geometry = new THREE.BoxGeometry(50,50,50);
console.log('顶点位置数据',geometry.attributes.position);
console.log('三角形顶点索引数据',geometry.index);

在这里插入图片描述

如果整个3D模型用一个长方体空间来表示,在三维空间xyz三个方向,都分割一次,这样就可以得到8个小的长方体子空间。

在这里插入图片描述

一个3D模型的三角形(顶点)分布在三维空间中,如果你用一个长方体来表示整个3d场景,当你分割为8个子空间的时候,每个子空间可以包含对应的三角形(顶点)数据。

每个子空间如果三角形(顶点)数量比较多,还可以继续分割,具体分割规则,你可以自定义,比如你可以规定,一个子空间包含的三角形数量只要大于8个就继续分割。这样一个个子空间可以构成一个树结构,整体来看,每个节点,分叉出来八个子节点。

在这里插入图片描述

项目引入Octree.js

Three.js在目录/examples/jsm/math/下提供了一个八叉树相关的扩展库Octree.js
npm安装threejs情况下,Octree.js扩展库引入路径。

// 引入/examples/jsm/math/目录下八叉树扩展库
import { Octree } from 'three/examples/jsm/math/Octree.js';

.html文件中,你也可以配置为其它任意路径引入方式

<script type="importmap">
    {
		"imports": {
			"three": "../../../three.js/build/three.module.js",
            "three/addons/": "../../../three.js/examples/jsm/"
		}
	}
</script>
<script  type="module">
import { Octree } from 'three/addons/math/Octree.js';
</script>

生成八叉树.fromGraphNode()

实例化一个八叉树对象。

const worldOctree = new Octree();

.fromGraphNode()的参数是模型对象,比如一个mesh,或者多个mesh构成的层级模型。

const gltf = await loader.loadAsync("../地形.glb");
worldOctree.fromGraphNode(gltf.scene);

执行.fromGraphNode()会对模型进行分割,分割为一个一个的小的长方体空间,构成一个八叉树。

在这里插入图片描述

执行.fromGraphNode()会把一个3D模型,分割为8个子空间,每个子空间都包含对应的三角形或者说顶点数据,每个子空间还可以继续分割。

具体分割规则非常复杂,不要求掌握,如果你有兴趣可以阅读Octree.js的源码,比如Octree.js会根据三角形数量决定是否分割一个子空间,比如一个子空间包含的三角形数量小于等于8个就不在分割,当然你也可以修改规则,作为初学者,也不要求记住,先有个印象就行。

Octree.js文件中部分源码截取

if ( len > 8 && level < 16 ) {
	subTrees[ i ].split( level + 1 );
}

浏览器控制台打印八叉树

浏览器控制台打印八叉树,查看分割的结果(不要求掌握,过一遍即可)。

console.log('查看八叉树结构', worldOctree);
  • .box属性是包围盒Box3,描述当前分割的子空间位置和尺寸
  • .subTrees属性表示八叉树的子节点,类似threejs层级模型的children属性
  • 查看叶子结点(最后一层没有子对象的节点).triangles属性,可以看到包含的三角形数据

OctreeHelper可视化八叉树

Three.js在目录/examples/jsm/helpers/下提供了一个可视化八叉树相关的扩展库OctreeHelper.js

import { OctreeHelper } from 'three/examples/jsm/helpers/OctreeHelper.js';
//课程案例源码里面配置的路径
import { OctreeHelper } from 'three/addons/helpers/OctreeHelper.js';
const helper = new OctreeHelper( worldOctree );
scene.add( helper );

在这里插入图片描述

在这里插入图片描述

八叉树与胶囊Capsule交叉计算

Capsule表示胶囊形状的几何体,具体说就是上面一个半球、中间一个圆柱、下面一个半球拼接构成的胶囊形状几何体。

下面给大家讲解胶囊形状几何体apsule与八叉树表示的3D模型进行交叉计算,你可以类比以前的射线交叉计算,虽然不同,都是都是交叉相关的计算。

项目引入胶囊碰撞体Capsule.js

Three.js在目录/examples/jsm/math/下提供了一个胶囊形状的几何体Capsule.js

npm安装threejs情况下,Capsule.js扩展库引入路径。

// 引入/examples/jsm/math/目录下胶囊扩展库Capsule.js
import { Capsule } from 'three/examples/jsm/math/Capsule.js';

本课程案例源码.html里面自定义了新的路径。

import { Capsule } from 'three/addons/math/Capsule.js';

创建胶囊几何体

创建胶囊几何体:让胶囊底部半球与y=0的平面刚好相切即可。

const R = 0.4;//胶囊半径
const H = 1.7;//胶囊总高度
const start = new THREE.Vector3(0, R, 0);//底部半球球心坐标
const end = new THREE.Vector3(0, H - R, 0);//顶部半球球心坐标
const capsule = new Capsule(start, end, R);
console.log('capsule', capsule);

练习:Mesh可视化上面胶囊几何体

// 可视化胶囊几何体
const capsuleHelper = CapsuleHelper(R, H);
model.add(capsuleHelper);
function CapsuleHelper(R, H) {
    const group = new THREE.Group();
    const material = new THREE.MeshLambertMaterial({
        color: 0x00ffff,
        transparent: true,
        opacity: 0.5,
    });
    // 底部半球
    const geometry = new THREE.SphereGeometry(R, 25, 25, 0, 2 * Math.PI, 0, Math.PI / 2);
    geometry.rotateX(Math.PI);
    const mesh = new THREE.Mesh(geometry, material);
    mesh.position.y = R;
    group.add(mesh)
    // 顶部半球
    const geometry2 = new THREE.SphereGeometry(R, 25, 25, 0, 2 * Math.PI, 0, Math.PI / 2);
    const mesh2 = new THREE.Mesh(geometry2, material);
    mesh2.position.set(0, H - R, 0)
    group.add(mesh2)
    // 中间圆柱
    const h = H - 2 * R
    const geometry3 = new THREE.CylinderGeometry(R, R, h,32,1,true);
    geometry3.translate(0, h / 2+R,0)
    const mesh3 = new THREE.Mesh(geometry3, material);
    group.add(mesh3)
    return group;
}

平移胶囊几何体

平移胶囊碰撞体,使底部半球位于y=0的平面以下。

capsule.translate(new THREE.Vector3(0, -R, 0));

可视化胶囊的模型对象同步平移

capsuleHelper.position.y += -R;

换一种平移方式

capsuleHelper.position.copy(capsule.start)
capsuleHelper.position.y -= R;

交叉计算

Octree.capsuleIntersect(capsule)可以计算Octree表示的3D模型与胶囊几何体capsule是否重合交叉,如果有重合交叉,返回交叉相关的信息,具体说就是在某个方向上交叉重合的深度是多少。

// 碰撞检测:几何体交叉计算
// Octree表示的3D模型和Capsule交叉计算
const result = worldOctree.capsuleIntersect(capsule);
console.log('碰撞检测结果', result);
  • .depth交叉重合的深度
  • .normal深度对应的方向

先与八叉树里面的包围盒子节点进行交叉计算,在与相交叉的包围盒包含的三角形进行交叉计算。借助八叉树,相比较,for循环所有模型所有三角形分别进行交叉计算,更节约时间。

胶囊放在斜面上

平移胶囊放在斜面上,查看交叉重合计算结果。

capsule.translate(new THREE.Vector3(0, 0, -3*H));
capsuleHelper.position.z += -3 * H;

你可以看到交叉方向不再是垂直于平面

  • .depth交叉重合的深度
  • .normal深度对应的方向

根据交叉碰撞数据,平移碰撞体

根据交叉碰撞数据,平移碰撞体,让胶囊碰撞体不在于八叉树对应模型重合。

  • .depth交叉重合的深度
  • .normal深度对应的方向

总结:.normal数据的特点就是让胶囊沿着.normal方向,平移.depth距离,就能刚好确保交叉重合深度为0

// 根据碰撞结果平移胶囊碰撞体,使交叉重合深度为0
capsule.translate(result.normal.multiplyScalar(result.depth));
capsuleHelper.position.copy(capsule.start);
capsuleHelper.position.y -= R;

胶囊与楼梯交叉

// 根据碰撞结果平移胶囊碰撞体,使交叉重合深度为0
capsule.translate(result.normal.multiplyScalar(result.depth));
capsuleHelper.position.copy(capsule.start);
capsuleHelper.position.y -= R;

平移后,不在于楼梯交叉,上升偏离地面了(如果运动起来,这个特点可以让胶囊产生上楼梯的效果,下节课会讲解)

胶囊向上平移,与地面没有接触,查看交叉计算结果

胶囊向上平移,与地面没有接触

// 胶囊向上平移,与地面不交叉情况,查看计算结果
capsule.translate(new THREE.Vector3(0, R, 0));
capsuleHelper.position.y += R;

交叉计算结果返回值false

const result = worldOctree.capsuleIntersect(capsule);
console.log('碰撞检测结果', result);
  • 20
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Threejs可视化

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值