Three.js使用WebWorker进行八叉树碰撞检测
经过一番探索后还是采用了整个碰撞检测都交给worker来做 原因
如果是小的模型还是不需要这么做的 js线程足够处理构建时的开销
步骤
- 将需要被检测的物体集合转换成可以背worker接收的结构化数据
- 发送给worker worker将结构化的数据转换成有效的Three元素集合并对其构建八叉树
fromGraphNode - 构建完成八叉树结构后 主线程发送胶囊体(或射线、球形)的信息到worker中 worker中根据碰撞体的信息构建有效的碰撞体
- 将检测结果返回
代码
结构化物体数据并传递到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();
相关文章:
Three.js使用WebWorker进行八叉树碰撞检测
经过一番探索后还是采用了整个碰撞检测都交给worker来做 原因 如果是小的模型还是不需要这么做的 js线程足够处理构建时的开销 步骤 将需要被检测的物体集合转换成可以背worker接收的结构化数据发送给worker worker将结构化的数据转换成有效的Three元素集合并对其构建八叉树fr…...
【教程】Notion笔记多平台设置中文显示
这个笔记软件界面挺好看,惊艳到了。 目录 网页版 桌面端 Windows版 Mac端 安卓端 网页版 直接安装这个插件即可,Chrome/Edge适用:Notion中文版 桌面端 都要去这个github下载语言包,用于替换文件:https://github.c…...
[牛客Hot101]链表篇
文章目录1.翻转链表2.链表内指定区间翻转3. 链表中的节点每k个一组翻转4. 合并两个排序的链表5. 合并k个排序的链表6. 判断链表是否有环7. 链表中倒数第k个节点8. 删除链表中的倒数第k和节点9. 两个链表的第一个公共节点10.链表的入环节点11. 链表相加(二࿰…...
Vue3 核心模块源码解析(上)
Vue3相比大家也都有所了解,即使暂时没有使用上,但肯定也学习过!Vue3是使用TS进行重写,采用了MonoRepo的管理方式进行管理,本篇文章我们一起来看看 Vue3的使用,与Vue2有什么区别,以及我们该如何优…...
【C进阶】指针的高级话题
文章目录:star:1. 字符指针:star:2. 指针数组2.1 指针数组的定义2.2 指针数组的使用:star:3. 数组指针3.1 数组的地址3.2 数组指针的使用:star:4. 数组参数和指针参数:star:5. 函数指针5.1 函数名和函数的地址5.2 练习:star:6. 函数指针数组6.1 转移表:star:7. 指向函数指针数组…...
无源晶振匹配电容—计算方法
以前有写过一篇文章“晶振”简单介绍了晶振的一些简单参数,今天我们来说下无源晶振的匹配电容计算方法: 如上图,是常见的的无源晶振常见接法,而今天来说到就是这种常见电路的电容计算方法,有两种: A&#…...
【测试】自动化测试03(JUnit)
努力经营当下,直至未来明朗! 文章目录JUnit一) 注解1. Test2. BeforeEach3. BeforeAll4. AfterEach5. AfterAll二) 断言(Assertions类)三)用例的执行顺序四)参数化五)测试…...
《计算机视觉和图像处理简介 - 中英双语版》:神经网络中的激活函数 ReLU vs Sigmoid
文章大纲 Neural Network Module and Training Function创建数据集Define Neural Network, Criterion function, Optimizer and Train the ModelTest Sigmoid and ReluAnalyze Results参考文献与学习路径在本文中,我们使用含有两个隐藏层的神经网络基于MNIST数据集测试Sigmoid…...
(三十七)大白话SQL标准中对事务的4个隔离级别,都是如何规定的呢?
之前我们给大家讲了数据库中多个事务并发时可能产生的几种问题,包括了脏写、脏读、不可重复读、幻读,几种问题 那么针对这些多事务并发的问题,实际上SQL标准中就规定了事务的几种隔离级别,用来解决这些问题。 注意一下ÿ…...
全国计算机等级考试三级网络技术考试大纲(2022年版)
全国计算机等级考试三级网络技术考试大纲(2022年版)基本要求 1. 了解大型网络系统规划、管理方法; 2. 具备中小型网络系统规划、设计的基本能力; 3. 掌握中小…...
服务器部署—若依【vue】如何部署到nginx里面?nginx刷新页面404怎么办?【完美解决建议收藏】
服务器部署项目我们大家都会遇到,但是有些铁子会遇到很多的问题,比如前端部署nginx如何操作? 前端有单纯的静态页面、还有前后端分离的项目;这里博主直接分享最牛最到位的前后端分离项目的前端部署到nginx上面,以若依项…...
算法练习(特辑)算法常用的数据结构、集合和方法总结
一、栈stack 1、初始化:Stack<Integer> st new Stack<Integer>(); 2、常用方法: boolean empty() :测试堆栈是否为空。Object peek( ):查看堆栈顶部的对象,但不从堆栈中移除它。Object pop( )ÿ…...
Apk转Aab(Android-App-Bundle)
这篇文章是参考Apk转Aab(Android-App-Bundle)_YoungBillsohu的博客-CSDN博客 基本照着这个大佬的步骤来就行,但是要注意的是apkTool最好是下新的,否则,会出现说一堆无语的错误,然后导致AAPT2关联资源的时候报错 类似这样的&#…...
大学物理期末大题专题训练总结-热学大题
今天下午去找郑老师权老师等去答疑,老师说大题会考查得比较套路,计算不难。明天就要考试了,再把大题常见题型总结一下,热学这块我做完了蓝本的热学题目,发现了如下三种:有关循环过程曲线的:给出…...
有趣的Hack-A-Sat黑掉卫星挑战赛——卫星平台内存dump
国家太空安全是国家安全在空间领域的表现。随着太空技术在政治、经济、军事、文化等各个领域的应用不断增加,太空已经成为国家赖以生存与发展的命脉之一,凝聚着巨大的国家利益,太空安全的重要性日益凸显[1]。而在信息化时代,太空安…...
OAK相机如何将yoloV8模型转换成blob格式?
编辑:OAK中国 首发:oakchina.cn 喜欢的话,请多多👍⭐️✍ 内容可能会不定期更新,官网内容都是最新的,请查看首发地址链接。 ▌前言 Hello,大家好,这里是OAK中国,我是助手…...
Python解题 - CSDN周赛第32期 - 运输石油(三维背包)
上期周赛因为最后一题出现bug,再加上都是经典的模板题,问哥就懒得写题解了。 本期也是有两道考过的题目,不过最后一题因为考到了背包问题的特殊类型,还是值得拿出来记个笔记。 第一题:传奇霸业 传奇霸业,是…...
JVM - G1垃圾收集器深入剖析
1、G1收集器概述 HotSpot团队一直努力朝着高效收集、减少停顿(STW: Stop The World)的方向努力,也贡献了从串行Serial收集器、到并行收集器Parallerl收集器,再到CMS并发收集器,乃至如今的G1在内的一系列优秀的垃圾收集器。 G…...
角度制与弧度制的相互转换np.deg2radnp.rad2deg
【小白从小学Python、C、Java】【计算机等级考试500强双证书】【Python-数据分析】角度制与弧度制的相互转换np.deg2radnp.rad2deg选择题以下关于python代码表述错误的一项是?import numpy as npprint("【执行】np.rad2deg(np.pi)")print(np.rad2deg(np.pi))print(&…...
【SAP Abap】X-DOC:SAP ABAP 语法更新之一(Open SQL新增特性)
SAP ABAP 语法更新之一(Open SQL新增特性)1、前言2、演示1、前言 自从 SAP 推出 SAP ON HANA,与之相随的 AS ABAP NW 7.40 版本以后,ABAP 语法也有了较多的更新,本篇对 Open Sql的语法更新部分做一个DEMO演示。 NW 7…...
Java 语言特性(面试系列2)
一、SQL 基础 1. 复杂查询 (1)连接查询(JOIN) 内连接(INNER JOIN):返回两表匹配的记录。 SELECT e.name, d.dept_name FROM employees e INNER JOIN departments d ON e.dept_id d.dept_id; 左…...
【Web 进阶篇】优雅的接口设计:统一响应、全局异常处理与参数校验
系列回顾: 在上一篇中,我们成功地为应用集成了数据库,并使用 Spring Data JPA 实现了基本的 CRUD API。我们的应用现在能“记忆”数据了!但是,如果你仔细审视那些 API,会发现它们还很“粗糙”:有…...
土地利用/土地覆盖遥感解译与基于CLUE模型未来变化情景预测;从基础到高级,涵盖ArcGIS数据处理、ENVI遥感解译与CLUE模型情景模拟等
🔍 土地利用/土地覆盖数据是生态、环境和气象等诸多领域模型的关键输入参数。通过遥感影像解译技术,可以精准获取历史或当前任何一个区域的土地利用/土地覆盖情况。这些数据不仅能够用于评估区域生态环境的变化趋势,还能有效评价重大生态工程…...
今日科技热点速览
🔥 今日科技热点速览 🎮 任天堂Switch 2 正式发售 任天堂新一代游戏主机 Switch 2 今日正式上线发售,主打更强图形性能与沉浸式体验,支持多模态交互,受到全球玩家热捧 。 🤖 人工智能持续突破 DeepSeek-R1&…...
OpenPrompt 和直接对提示词的嵌入向量进行训练有什么区别
OpenPrompt 和直接对提示词的嵌入向量进行训练有什么区别 直接训练提示词嵌入向量的核心区别 您提到的代码: prompt_embedding = initial_embedding.clone().requires_grad_(True) optimizer = torch.optim.Adam([prompt_embedding...
【从零学习JVM|第三篇】类的生命周期(高频面试题)
前言: 在Java编程中,类的生命周期是指类从被加载到内存中开始,到被卸载出内存为止的整个过程。了解类的生命周期对于理解Java程序的运行机制以及性能优化非常重要。本文会深入探寻类的生命周期,让读者对此有深刻印象。 目录 …...
MySQL JOIN 表过多的优化思路
当 MySQL 查询涉及大量表 JOIN 时,性能会显著下降。以下是优化思路和简易实现方法: 一、核心优化思路 减少 JOIN 数量 数据冗余:添加必要的冗余字段(如订单表直接存储用户名)合并表:将频繁关联的小表合并成…...
适应性Java用于现代 API:REST、GraphQL 和事件驱动
在快速发展的软件开发领域,REST、GraphQL 和事件驱动架构等新的 API 标准对于构建可扩展、高效的系统至关重要。Java 在现代 API 方面以其在企业应用中的稳定性而闻名,不断适应这些现代范式的需求。随着不断发展的生态系统,Java 在现代 API 方…...
Scrapy-Redis分布式爬虫架构的可扩展性与容错性增强:基于微服务与容器化的解决方案
在大数据时代,海量数据的采集与处理成为企业和研究机构获取信息的关键环节。Scrapy-Redis作为一种经典的分布式爬虫架构,在处理大规模数据抓取任务时展现出强大的能力。然而,随着业务规模的不断扩大和数据抓取需求的日益复杂,传统…...
Qt 事件处理中 return 的深入解析
Qt 事件处理中 return 的深入解析 在 Qt 事件处理中,return 语句的使用是另一个关键概念,它与 event->accept()/event->ignore() 密切相关但作用不同。让我们详细分析一下它们之间的关系和工作原理。 核心区别:不同层级的事件处理 方…...
