当前位置: 首页 > news >正文

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

经过一番探索后还是采用了整个碰撞检测都交给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();

相关文章:

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

经过一番探索后还是采用了整个碰撞检测都交给worker来做 原因 如果是小的模型还是不需要这么做的 js线程足够处理构建时的开销 步骤 将需要被检测的物体集合转换成可以背worker接收的结构化数据发送给worker worker将结构化的数据转换成有效的Three元素集合并对其构建八叉树fr…...

【教程】Notion笔记多平台设置中文显示

这个笔记软件界面挺好看&#xff0c;惊艳到了。 目录 网页版 桌面端 Windows版 Mac端 安卓端 网页版 直接安装这个插件即可&#xff0c;Chrome/Edge适用&#xff1a;Notion中文版 桌面端 都要去这个github下载语言包&#xff0c;用于替换文件&#xff1a;https://github.c…...

[牛客Hot101]链表篇

文章目录1.翻转链表2.链表内指定区间翻转3. 链表中的节点每k个一组翻转4. 合并两个排序的链表5. 合并k个排序的链表6. 判断链表是否有环7. 链表中倒数第k个节点8. 删除链表中的倒数第k和节点9. 两个链表的第一个公共节点10.链表的入环节点11. 链表相加&#xff08;二&#xff0…...

Vue3 核心模块源码解析(上)

Vue3相比大家也都有所了解&#xff0c;即使暂时没有使用上&#xff0c;但肯定也学习过&#xff01;Vue3是使用TS进行重写&#xff0c;采用了MonoRepo的管理方式进行管理&#xff0c;本篇文章我们一起来看看 Vue3的使用&#xff0c;与Vue2有什么区别&#xff0c;以及我们该如何优…...

【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. 指向函数指针数组…...

无源晶振匹配电容—计算方法

以前有写过一篇文章“晶振”简单介绍了晶振的一些简单参数&#xff0c;今天我们来说下无源晶振的匹配电容计算方法&#xff1a; 如上图&#xff0c;是常见的的无源晶振常见接法&#xff0c;而今天来说到就是这种常见电路的电容计算方法&#xff0c;有两种&#xff1a; A&#…...

【测试】自动化测试03(JUnit)

努力经营当下&#xff0c;直至未来明朗&#xff01; 文章目录JUnit一&#xff09; 注解1. Test2. BeforeEach3. BeforeAll4. AfterEach5. AfterAll二&#xff09; 断言&#xff08;Assertions类&#xff09;三&#xff09;用例的执行顺序四&#xff09;参数化五&#xff09;测试…...

《计算机视觉和图像处理简介 - 中英双语版》:神经网络中的激活函数 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个隔离级别,都是如何规定的呢?

之前我们给大家讲了数据库中多个事务并发时可能产生的几种问题&#xff0c;包括了脏写、脏读、不可重复读、幻读&#xff0c;几种问题 那么针对这些多事务并发的问题&#xff0c;实际上SQL标准中就规定了事务的几种隔离级别&#xff0c;用来解决这些问题。 注意一下&#xff…...

全国计算机等级考试三级网络技术考试大纲(2022年版)

全国计算机等级考试三级网络技术考试大纲&#xff08;2022年版&#xff09;基本要求 &#xff11;&#xff0e; 了解大型网络系统规划、管理方法&#xff1b; &#xff12;&#xff0e; 具备中小型网络系统规划、设计的基本能力&#xff1b; &#xff13;&#xff0e; 掌握中小…...

服务器部署—若依【vue】如何部署到nginx里面?nginx刷新页面404怎么办?【完美解决建议收藏】

服务器部署项目我们大家都会遇到&#xff0c;但是有些铁子会遇到很多的问题&#xff0c;比如前端部署nginx如何操作&#xff1f; 前端有单纯的静态页面、还有前后端分离的项目&#xff1b;这里博主直接分享最牛最到位的前后端分离项目的前端部署到nginx上面&#xff0c;以若依项…...

算法练习(特辑)算法常用的数据结构、集合和方法总结

一、栈stack 1、初始化&#xff1a;Stack<Integer> st new Stack<Integer>(); 2、常用方法&#xff1a; boolean empty() &#xff1a;测试堆栈是否为空。Object peek( )&#xff1a;查看堆栈顶部的对象&#xff0c;但不从堆栈中移除它。Object pop( )&#xff…...

Apk转Aab(Android-App-Bundle)

这篇文章是参考Apk转Aab(Android-App-Bundle)_YoungBillsohu的博客-CSDN博客 基本照着这个大佬的步骤来就行&#xff0c;但是要注意的是apkTool最好是下新的&#xff0c;否则&#xff0c;会出现说一堆无语的错误&#xff0c;然后导致AAPT2关联资源的时候报错 类似这样的&#…...

大学物理期末大题专题训练总结-热学大题

今天下午去找郑老师权老师等去答疑&#xff0c;老师说大题会考查得比较套路&#xff0c;计算不难。明天就要考试了&#xff0c;再把大题常见题型总结一下&#xff0c;热学这块我做完了蓝本的热学题目&#xff0c;发现了如下三种&#xff1a;有关循环过程曲线的&#xff1a;给出…...

有趣的Hack-A-Sat黑掉卫星挑战赛——卫星平台内存dump

国家太空安全是国家安全在空间领域的表现。随着太空技术在政治、经济、军事、文化等各个领域的应用不断增加&#xff0c;太空已经成为国家赖以生存与发展的命脉之一&#xff0c;凝聚着巨大的国家利益&#xff0c;太空安全的重要性日益凸显[1]。而在信息化时代&#xff0c;太空安…...

OAK相机如何将yoloV8模型转换成blob格式?

编辑&#xff1a;OAK中国 首发&#xff1a;oakchina.cn 喜欢的话&#xff0c;请多多&#x1f44d;⭐️✍ 内容可能会不定期更新&#xff0c;官网内容都是最新的&#xff0c;请查看首发地址链接。 ▌前言 Hello&#xff0c;大家好&#xff0c;这里是OAK中国&#xff0c;我是助手…...

Python解题 - CSDN周赛第32期 - 运输石油(三维背包)

上期周赛因为最后一题出现bug&#xff0c;再加上都是经典的模板题&#xff0c;问哥就懒得写题解了。 本期也是有两道考过的题目&#xff0c;不过最后一题因为考到了背包问题的特殊类型&#xff0c;还是值得拿出来记个笔记。 第一题&#xff1a;传奇霸业 传奇霸业&#xff0c;是…...

JVM - G1垃圾收集器深入剖析

​​​​​​​1、G1收集器概述 HotSpot团队一直努力朝着高效收集、减少停顿(STW: Stop The World)的方向努力&#xff0c;也贡献了从串行Serial收集器、到并行收集器Parallerl收集器&#xff0c;再到CMS并发收集器&#xff0c;乃至如今的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 语法更新之一&#xff08;Open SQL新增特性&#xff09;1、前言2、演示1、前言 自从 SAP 推出 SAP ON HANA&#xff0c;与之相随的 AS ABAP NW 7.40 版本以后&#xff0c;ABAP 语法也有了较多的更新&#xff0c;本篇对 Open Sql的语法更新部分做一个DEMO演示。 NW 7…...

【改进灰狼优化算法】改进收敛因子和比例权重的灰狼优化算法【期刊论文完美复现】(Matlab代码实现)

&#x1f468;‍&#x1f393;个人主页&#xff1a;研学社的博客&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5;&#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密…...

Linux C代码获取线程ID

Linux C代码获取线程ID gettid可以获取线程id,但是通过man gettid可以看到下面这两句 也就是说glibc没有为这个gettid封装系统调用&#xff0c;需要使用syscall。 #define _GNU_SOURCE#include <unistd.h>#include <sys/syscall.h>#include <sys/types.h>pi…...

基本密码技术

AESAES取代DES&#xff0c;是一种对称加密技术&#xff0c;分为AES-128/192/256, 其分组长度固定为128b&#xff0c;若最后一个分组长度不够&#xff0c;需要补全至128b长度。所支持的秘钥长度分别为128b/192b/256b.分组密码模式AES是对明文进行分组之后逐块进行加密&#xff0…...

【力扣周赛#334】6369. 左右元素和的差值 + 6368. 找出字符串的可整除数组 + 6367. 求出最多标记下标

目录 6369. 左右元素和的差值 - 前缀后缀和 ac 6368. 找出字符串的可整除数组 - 操作余数ac 6367. 求出最多标记下标 - 二分答案 贪心 6369. 左右元素和的差值 - 前缀后缀和 ac class Solution {public int[] leftRigthDifference(int[] nums) {int nnums.length;int[] re…...

行测-判断推理-图形推理-位置规律-平移

位置平移&#xff0c;选D空白每次顺时针移动一格&#xff0c;黑色圆每次逆时针移动2格选C两个黑色⚪&#xff0c;每次顺时针移动2格白色⚪&#xff0c;先到对角位置&#xff0c;再顺时针移动一格选B三角形的底&#xff0c;顺时针移动三角形的顶点&#xff0c;在正方形的内部顺时…...

数据库基础知识(一)

目录 什么是数据库 表&#xff0c;列&#xff0c;行 主键 什么是SQL 什么是数据库 数据库(database):保存有组织的数据的容器&#xff08;通常是一个文件或一组文件&#xff09;。 数据库软件(DMBS):又名数据库管理系统。数据库是通过数据库软件创建和操纵的容器。因为你并…...

MyBatis 的工作原理解析

文章目录前言一、mybatis工作原理1.1 流程图1.2 步骤解析1.3 代码实现前言 本文记录 Mybatis 的工作原理&#xff0c;做到知识梳理总结的作用。 一、mybatis工作原理 Mybatis 的总体工作原理流程图如下图所示 1.1 流程图 1.2 步骤解析 Mybatis 框架在工作时大致经过8个步骤…...

终端软件架构说

目录 零&#xff1a;前言 一&#xff0c;基于服务的架构 二&#xff0c;基于多进程多线程的架构 三&#xff0c;以数据为中心的架构 四&#xff0c;类Android的分层架构设计 五&#xff0c;总结 零&#xff1a;前言 谈到架构&#xff0c;可能大家的第一感觉是信息系统的…...

LearnOpenGL-入门-你好,三角形

本人刚学OpenGL不久且自学&#xff0c;文中定有代码、术语等错误&#xff0c;欢迎指正 我写的项目地址&#xff1a;https://github.com/liujianjie/LearnOpenGLProject LearnOpenGL中文官网&#xff1a;https://learnopengl-cn.github.io/ 文章目录图形渲染管线基本介绍着色器…...

SOEM 源码解析 ecx_init_redundant

/* Initialise lib in redundant NIC mode* 在冗余网卡模式下初始化lib库* param[in] context context struct* 上下文结构体* param[in] redport pointer to redport, redundant port data* 指向冗余端口的指针&#xff…...

dede手机网站跳转/网页设计制作教程

很久很久以前我是个.net程序媛&#xff0c;几年前也做过一些php的项目&#xff0c;不过我大部分时间都是一个前端设计师(工程师)。本来我想自己写api&#xff0c;但是一上手&#xff0c;才发现&#xff0c;忘了。。。99%忘了&#xff0c;孕傻害死人。算了呢&#xff0c;就用小程…...

服务器多少钱/seo网站推广建站服务商

一篇很好的讲解嵌入式linux启动的文章嵌入式Linux启动分为两个部分&#xff0c;系统引导与Linux启动。系统引导将完成Linux装入内存前&#xff0c;初始化CPU和相关IO设备&#xff0c;并将Linux调入内存的工作。系统引导主要由BootLoader实现。在BootLoader将Linux内核调入内存之…...

没备案的网站/网站推广优化外包公司

linux目录结构CentOS-8 目录结构/bin/存放系统命令的目录&#xff0c;普通用户和超级用户都可以执行。不过放在/bin下的命令在单用户模式下也可以执行/boot/系统启动目录&#xff0c;保存系统启动相关的文件&#xff0c;如内核文件和启动引导程序&#xff08;grub&#xff09;文…...

wordpress 4.7.4 主题/磁力搜索引擎哪个好

今天甲方通知要统计一下我们协议栈代码的行数&#xff0c;好久没有关心过这样的问题&#xff0c;上一次统计代码行数好像是好多年前的事情了&#xff0c;也忘记了用的什么工具。最开始想用NLOC&#xff0c;因为需要.NET 2.0&#xff0c;我的机器装不上。为了这个工具安装.NET 2…...

南京网站优化网站建设公司/如何引流客源最快的方法

第一种&#xff1a;使用正则 复制代码 代码如下:<?php echo preg_replace(# #, , ab ab); //输出 "abab" ?>第二种&#xff1a;使用str_replace()函数 复制代码 代码如下:<?php echo str_replace( , , ab ab); //输出 "abab ?>第三种&…...

做网站优化的话术/在线crm软件

这里有几个定义需要说一下&#xff0c;外设&#xff0c;顾名思义&#xff0c;就是IC芯片所接的能够与IC通信的外部设备。早起由于IC集成工艺不发达&#xff0c;很多东西都是外设的&#xff0c;在此以DSP芯片为例&#xff0c;比如PWM、ADC、CAN等等&#xff0c;原本都是需要芯片…...