首页 > 其他分享 >Three.js使用WebWorker进行八叉树碰撞检测

Three.js使用WebWorker进行八叉树碰撞检测

时间:2023-03-05 10:04:03浏览次数:54  
标签:playerCollider const 八叉树 worker Three 碰撞检测 result ._ new


经过一番探索后还是采用了整个碰撞检测都交给worker来做 ​​原因​​ 如果是小的模型还是不需要这么做的 js线程足够处理构建时的开销

步骤

  1. 将需要被检测的物体集合转换成可以背worker接收的​​结构化数据​
  2. 发送给worker worker将结构化的数据转换成有效的Three元素集合并对其构建八叉树​​fromGraphNode​
  3. 构建完成八叉树结构后 主线程发送胶囊体(或射线、球形)的信息到worker中 worker中根据碰撞体的信息构建有效的碰撞体
  4. 将检测结果返回

代码

结构化物体数据并传递到worker

const modelStruct = ModelTranslate.generateWorkerStruct(obj);
this.worker.postMessage({ type: "build", modelStruct });

worker接收并转换成有效的数据并进行八叉树构建

onmessage = function (e) {
switch (e.data.type) {
case "build":
const model = ModelTranslate.parseWorkerStruct(e.data.modelStruct);
buildFromGraphNode.fromGraphNode(model);
break;
};

主线程发送胶囊体的信息到worker中

this.playerCollider = new Capsule();
//...
this.worker.postMessage({
type: "collider",
collider: this.playerCollider,
});

worker中根据碰撞体的信息构建有效的碰撞体 将检测结果返回

onmessage = function (e) {
switch (e.data.type) {
case "build":
const model = ModelTranslate.parseWorkerStruct(e.data.modelStruct);
buildFromGraphNode.fromGraphNode(model);
break;
case "collider":
buildFromGraphNode.collider(e.data.collider);
break;
}
};

webWorker代码

/*
* @Author: hongbin
* @Date: 2023-02-25 12:06:52
* @LastEditors: hongbin
* @LastEditTime: 2023-02-26 19:21:06
* @Description: 八叉树构建worker
*/
import { Capsule } from "three/examples/jsm/math/Capsule";
import { Octree } from "../expand/Octree";
import { ModelTranslate } from "./ModelTranslate";

class BuildFromGraphNode {
worldOctree!: Octree;
playerCollider!: Capsule;
constructor() {
this.init();
}

init() {
this.playerCollider = new Capsule();
this.worldOctree = new Octree();
}

collider({
start,
end,
radius,
}: {
start: { x: number; y: number; z: number };
end: { x: number; y: number; z: number };
radius: number;
}) {
this.playerCollider.start.set(start.x, start.y, start.z);
this.playerCollider.end.set(end.x, end.y, end.z);
this.playerCollider.radius = radius;

const result = this.worldOctree.capsuleIntersect(this.playerCollider);

postMessage({
type: "colliderResult",
result,
});
}

fromGraphNode(obj: Object3D) {
const start = performance.now();
this.worldOctree.fromGraphNode(obj);
postMessage({
type: "graphNodeBuildComplete",
msg: `构建八叉树结构成功 用时 ${performance.now() - start}`,
// graphNode: this.worldOctree,
});
}
}

const buildFromGraphNode = new BuildFromGraphNode();

/**
* 监听主线程发来的数信息
*/
onmessage = function (e) {
switch (e.data.type) {
case "connect":
postMessage({
msg: "连接成功",
});
break;
case "build":
const model = ModelTranslate.parseWorkerStruct(e.data.modelStruct);
buildFromGraphNode.fromGraphNode(model);
break;
case "collider":
buildFromGraphNode.collider(e.data.collider);
break;
}
};

export {};

八叉树控制器代码

/*
* @Author: hongbin
* @Date: 2023-02-02 13:37:11
* @LastEditors: hongbin
* @LastEditTime: 2023-02-26 19:23:22
* @Description: 八叉树控制器
*/
import * as THREE from "three";
import { Sphere, Vector3 } from "three";
import { Capsule } from "../expand/Capsule";
import { Octree } from "../expand/Octree";
import { Octree as WorkerOctree } from "../expand/WorkerOctree";
import { ThreeHelper } from "@/src/ThreeHelper";
import { OctreeHelper } from "three/examples/jsm/helpers/OctreeHelper";
import { ModelTranslate } from "../worker/ModelTranslate";

interface VVector3 {
x: number;
y: number;
z: number;
}

interface IWorkerSubTree {
box: {
isBox3: true;
max: VVector3;
min: VVector3;
};
triangles: { a: VVector3; b: VVector3; c: VVector3 }[];
subTrees: IWorkerSubTree[];
}

export class OctreeControls {
private worldOctree: Octree;
private worldWorldOctree?: WorkerOctree;
playerCollider: Capsule;
playerOnFloor = false;
/** 构建八叉树完成 **/
buildComplete = false;
private _collide = (result: any) => {};
private _player?: Object3D;
private _box = new THREE.Box3();
private worker?: Worker;
private _normal = new Vector3();
// octreeHelper: OctreeHelper;

constructor() {
this.worldOctree = new Octree();
this.playerCollider = new Capsule();
//@ts-ignore
// this.octreeHelper = new OctreeHelper(this.worldOctree, 0xff0);
this.useWebWorker();
}

/**
* 碰撞回调
*/
collide(_collide: (result?: { normal: Vector3; depth: number }) => void) {
this._collide = _collide;
}

/**
* 与球体进行碰撞
*/
sphereCollider(sphere: Sphere) {
const result = this.worldOctree.sphereIntersect(sphere);
return result;
}

handleCollider(
result: ReturnType<typeof this.worldOctree["capsuleIntersect"]>
) {
this.playerOnFloor = false;
if (result) {
this.playerOnFloor = result.normal.y > 0;
}
if (this.isLog) {
console.log(result, this.playerOnFloor);
}
this._collide(result);
}

/**
* 碰撞检测
*/
playerCollisions() {
if (!this.buildComplete) return (this.playerOnFloor = true);
// 如果启用了webworker 交给worker处理
if (this.worker) {
this.worker.postMessage({
type: "collider",
collider: this.playerCollider,
});
return;
}

const world = this.worldWorldOctree
? this.worldWorldOctree
: this.worldOctree;
const result = world.capsuleIntersect(this.playerCollider);
this.handleCollider(result);
}

private isLog = false;
console(gui: ThreeHelper["gui"]) {
const plane = {
log: () => {
this.isLog = !this.isLog;
},
helper: () => {
this.helper();
},
};
gui?.add(plane, "log").name("打印八叉树检测结果");
gui?.add(plane, "helper").name("查看八叉树碰撞体");
}

private _translate = (v: Vector3) => {};

onTranslate(call: typeof this._translate) {
this._translate = call;
}

/**
* 增量更新胶囊体的位置
* 应同步人物的移动
*/
translatePlayerCollider(v: Vector3) {
this.playerCollider.translate(v);
this._translate(v);
}

playerBox3() {
return this._box;
}

helper() {
if (!this._player) return;
const radius = this.playerCollider.radius;
const start = this.playerCollider.start;
const end = this.playerCollider.end;
{
const mesh = ThreeHelper.instance.generateRect(
{
width: 0.1,
height: 0.01,
depth: 0.01,
},
{ color: 0x00ffa0 }
);
mesh.position.copy(this.playerCollider.start);
// mesh.position.y -= radius;
ThreeHelper.instance.add(mesh);
}
{
const mesh = ThreeHelper.instance.generateRect(
{
width: 0.1,
height: 0.01,
depth: 0.01,
},
{ color: 0xff3a00 }
);
mesh.position.copy(this.playerCollider.end);
// mesh.position.y += radius;
ThreeHelper.instance.add(mesh);
}
{
this._player.updateWorldMatrix(false, false);

const Capsule = new THREE.Group();
Capsule.applyMatrix4(this._player.matrixWorld);

const length = start.clone().sub(end).length();
const geometry = new THREE.CapsuleGeometry(radius, length, 4, 8);
const material = new THREE.MeshBasicMaterial({
color: 0x00ff00,
wireframe: true,
});
const capsule = new THREE.Mesh(geometry, material);
Capsule.add(capsule);
Capsule.position.y += length / 2 + radius;
// this._player.add(Capsule);
ThreeHelper.instance.add(Capsule);
}
}

/**
* 计算碰撞体的胶囊体(碰撞范围 胶囊的高度 和半径)
*/
computeCollider() {
if (!this._player) throw new Error("未执行 player() 方法");
const size = this._player.userData._size;
// 半径取 宽和长 大的一侧
const radius = Math.max(size.x, size.z) / 2;
const { x, y, z } = this._player.position;

const collider = {
// 头
start: new THREE.Vector3(x, y + size.y - radius, z),
// 脚
end: new THREE.Vector3(x, y + radius - 0.01, z),
radius,
};

return collider;
}

/**
* 传入玩家对象 计算玩家胶囊体的数据
*/
player(obj: Object3D) {
this._player = obj;
const defaultCollider = this.computeCollider();

this.playerCollider.start.copy(defaultCollider.start);
this.playerCollider.end.copy(defaultCollider.end);
this.playerCollider.radius = defaultCollider.radius;
}

/**
* 根据传入对象构建该对象的八叉树结构
* 模型越大越耗时
*/
fromGraphNode(obj: Object3D, call?: VoidFunction) {
if (this.worker) {
const modelStruct = ModelTranslate.generateWorkerStruct(obj);
this.worker.postMessage({ type: "build", modelStruct });
} else {
this.worldOctree.fromGraphNode(obj);
console.log(this.worldOctree);
// this.octreeHelper.update();
this.buildComplete = true;
call && call();
}
}

/**
* 格式化从web worker中拿到的八叉树结构
* 开销也非常大虽然只是格式转变但要便利的次数依然十分庞大还是会对线程造成堵塞
*/
formatSubTrees(subTree: IWorkerSubTree) {
const octree = new Octree();
const min = new THREE.Vector3().copy(subTree.box.min as Vector3);
const max = new THREE.Vector3().copy(subTree.box.max as Vector3);
octree["box"] = new THREE.Box3(min, max);
octree["triangles"] = subTree.triangles.map((triangle) => {
const a = new THREE.Vector3().copy(triangle.a as Vector3);
const b = new THREE.Vector3().copy(triangle.b as Vector3);
const c = new THREE.Vector3().copy(triangle.c as Vector3);
return new THREE.Triangle(a, b, c);
});
octree.subTrees = subTree.subTrees.map((subTree) =>
this.formatSubTrees(subTree)
);
return octree;
}

/**
* 使用从web worker 构建的八叉树结构
*/
updateGraphNode(subTree: IWorkerSubTree, call?: VoidFunction) {
// const Octree = this.formatSubTrees(subTrees);
this.worldWorldOctree = new WorkerOctree(subTree);
this.buildComplete = true;
call && call();
}

/**
* 使用webWorker进行八叉树构建、检测
*/
useWebWorker() {
/** 构建八叉树的web worker */
const worker = new Worker(
new URL("../worker/OctreeBuild.ts", import.meta.url)
);

worker.onmessage = (e) => {
this.worker = worker;
if (e.data.type === "graphNodeBuildComplete") {
console.log("八叉树构建完成", e.data);
this.buildComplete = true;
} else if (e.data.type == "colliderResult") {
if (e.data.result) {
const { normal } = e.data.result;
this._normal.copy(normal);
e.data.result.normal = this._normal;
}
this.handleCollider(e.data.result);
if (this.isLog) {
console.log(e.data);
}
}
};
worker.postMessage({ type: "connect" });

worker.onerror = (err) => {
console.error("work出错:", err, err.message);
};
}
}

主线程使用八叉树控制器代码

//初始化
const octreeControls = new OctreeControls();
//构建八叉树
octreeControls.fromGraphNode(group);
//碰撞检测回调
octreeControls.collide((result) => {
// 碰撞的方向 * 深度
if (result) {
const v = result.normal.multiplyScalar(result.depth);
v.y -= 0.0001;
//根据碰撞的方向和深度 移动人物和碰撞题
ObserverControl.translate(v);
}
});
//逐帧检测
octreeControls.playerCollisions();


标签:playerCollider,const,八叉树,worker,Three,碰撞检测,result,._,new
From: https://blog.51cto.com/u_15964288/6101109

相关文章

  • 题解 CF1406D【Three Sequences】
    看错题了,我很生气。problemYouaregivenasequenceof$n$integers$a_1,a_2,\ldots,a_n$.Youhavetoconstructtwosequencesofintegers$b$and$c......
  • Three.js实现高程数据加载
    通过加载高程数据(dem),显示地形高低起伏,达到良好的立体展示效果;Three.js能够通过设置一系列坐标点的高度,构建成面的形式,显示高程数据。实现方式:使用Three.js的PlaneGeometry进......
  • InstancedMesh threejs 批量重复使用相同的物体和材质
    代码<!DOCTYPEhtml><htmllang="en"> <head> <title>three.jswebgl-instancing-raycast</title> <metacharset="utf-8"/> <meta name="viewport" ......
  • 踩坑(二) --- threejs 踩坑之点击模型,获取点击位置,对应的世界坐标
      这是原型中的效果,需要在模型上添加自定义标签,那么步骤大致就是:点击模型,获取到对应位置的世界坐标,存入到数据库。主页展示的时候,先查出这些点,然后通过css2dObject......
  • thirty-two(模型点击展示)react-three-fiber
    模型点击蒙版展示点击展示目的(用户需要看见模型中更加多的内容信息)使用技术ThreeJs、React-three-fiber、React-three-drei、React、css整体思路:  1、在展示模型中......
  • threejs shader特效,分区辉光
    分区辉光有两种实现方式:1.单个图层两次渲染,先用带bloom的composer渲染一次,再正常渲染一次:https://github.com/mrdoob/three.js/blob/master/examples/webgl_postprocessin......
  • 在vue中使用threejs,建3D图
    前言:记录在vue中使用threejs步骤:一、安装npminstallthree--save安装完成如下图:二、引入//引入threejs核心模块import*asTHREEfrom"three"//引入Orbi......
  • 使用 Three.js 的 3D 制作动画场景
    推荐:将 NSDT场景编辑器 加入你的3D开发工具链。由于GSL语法的复杂性,对于许多开发人员来说WebGL是一个未知的领域。但是有了Three.js,在浏览器中3D的实现变得简单......
  • vue3 ThreeJS 引入obj模型过暗的问题
      当我单纯地用MTLLoader引入材质,OBJLoader引入模型并添加到场景中时, 发现模型非常得暗. 需要将环境光的强度设置到3.5左右看起来才比较正常. 但正常情况下环境光......
  • Maptalk-Three-Vue简单示例
    Maptalk-Three-Vue简单示例​ 通过国产GIS前端框架付镇大神的[Maptalks](maptalks/maptalks.js:AlightandplugableJavaScriptlibraryforintegrated2D/3Dmaps.......