广州城市建设档案网站,wordpress门户建站,查企业app,摄影类全屏式展示的wordpress主题免费下载Three.js 顶点射线碰撞检测实现步骤详解
一、基本思路
核心算法流程#xff1a;
第1步#xff1a;遍历几何体所有顶点#xff0c;分别创建与几何体中心坐标构成的射线
对于 每个几何体A 的 每个顶点V#xff1a;顶点位置 V 的世界坐标位置中心位置 几何体A 的世界坐标中心…Three.js 顶点射线碰撞检测实现步骤详解一、基本思路核心算法流程第1步遍历几何体所有顶点分别创建与几何体中心坐标构成的射线对于 每个几何体A 的 每个顶点V 顶点位置 V 的世界坐标位置 中心位置 几何体A 的世界坐标中心点 方向向量 中心位置 - 顶点位置 创建射线Raycaster 起点 顶点位置 方向 方向向量归一化实现细节获取几何体的顶点坐标数组Float32Array每3个值表示一个顶点的x,y,z坐标将顶点从局部坐标系转换到世界坐标系计算几何体的世界中心点通常是物体位置对每个顶点创建从顶点指向中心的射线第2步射线交叉计算对于 几何体A 的 每条射线R 使用Raycaster的intersectObject()方法 检测射线R 是否与 几何体B 相交 如果相交 获取交点信息交点坐标、距离、面信息等关键点Raycaster.intersectObject(几何体B) 返回交点数组交点数组按距离从近到远排序如果射线与几何体B有交点说明这条射线可能穿过了几何体B第3步通过距离判断两个网格模型是否碰撞对于 几何体A 的 每条射线R 的 每个交点I 计算起点到交点的距离 距离1 计算起点到几何体A中心的距离 距离2 如果 距离1 距离2 说明射线在到达自己中心之前就击中了对方 判断为这条射线穿过了对方几何体 如果 至少有一条射线穿过对方几何体 判断为两个几何体发生碰撞逻辑解释射线是从自己顶点指向自己中心的如果射线在到达自己中心之前就击中了对方说明对方在自己内部或前方如果射线先到达自己中心说明对方在自己后方或外部二、具体实现步骤详解Step 1: 获取几何体所有顶点世界坐标// 1.1 获取几何体的顶点位置数据constgeometrymesh.geometryconstpositionAttributegeometry.attributes.positionconstpositionspositionAttribute.array// [x1, y1, z1, x2, y2, z2, ...]// 1.2 获取世界变换矩阵constmatrixWorldmesh.matrixWorld// 1.3 遍历所有顶点转换为世界坐标constworldVertices[]for(leti0;ipositions.length;i3){// 创建局部坐标顶点constlocalVertexnewTHREE.Vector3(positions[i],// xpositions[i1],// ypositions[i2]// z)// 转换为世界坐标constworldVertexlocalVertex.clone()worldVertex.applyMatrix4(matrixWorld)worldVertices.push(worldVertex)}Step 2: 计算几何体中心点世界坐标// 方法1直接使用mesh的世界位置对于对称几何体constcenternewTHREE.Vector3()mesh.getWorldPosition(center)// 方法2计算所有顶点的平均值更精确letsumnewTHREE.Vector3(0,0,0)for(constvertexofworldVertices){sum.add(vertex)}constcentersum.divideScalar(worldVertices.length)Step 3: 创建顶点到中心的射线// 3.1 创建Raycaster实例constraycasternewTHREE.Raycaster()// 3.2 对于每个顶点创建从顶点指向中心的射线for(constvertexofworldVertices){// 计算方向向量从顶点指向中心constdirectioncenter.clone().sub(vertex).normalize()// 设置射线raycaster.set(vertex,direction)// 现在raycaster就代表了一条从顶点指向中心的射线// 我们可以用它来检测与其他几何体的交点}Step 4: 射线交叉计算// 4.1 检测射线是否与另一个几何体相交constotherMesh...// 另一个几何体constintersectsraycaster.intersectObject(otherMesh)// 4.2 分析交点信息if(intersects.length0){constfirstIntersectintersects[0]// 最近的交点// 交点信息包含// - point: 交点坐标Vector3// - distance: 从射线起点到交点的距离// - object: 被击中的物体// - face: 被击中的面// - faceIndex: 面的索引}Step 5: 通过距离判断是否碰撞// 5.1 计算关键距离constdistanceToIntersectvertex.distanceTo(firstIntersect.point)// 顶点到交点的距离constdistanceToCentervertex.distanceTo(center)// 顶点到中心的距离// 5.2 判断逻辑if(distanceToIntersectdistanceToCenter){// 情况射线在到达自己中心之前就击中了对方// 说明对方几何体在自己前方或内部// 这很可能表示两个几何体相交或碰撞console.log(这条射线穿过了对方几何体)// 记录碰撞信息collisions.push({vertex:vertex,intersectPoint:firstIntersect.point,rayDirection:direction})}else{// 情况射线先到达自己中心然后才可能击中对方// 说明对方几何体在自己后方或外部// 这很可能表示两个几何体没有碰撞}Step 6: 综合判断碰撞// 6.1 统计所有穿过的射线letcollisionCount0for(所有顶点射线){if(射线穿过了对方几何体){collisionCount}}// 6.2 判断是否发生碰撞if(collisionCount0){console.log(发生碰撞共有${collisionCount}条射线穿过对方几何体)returntrue// 发生碰撞}else{console.log(没有碰撞)returnfalse// 没有碰撞}三、数学原理图解情况1发生碰撞射线穿过对方 顶点 │ │ 距离1到交点 │ ▼ 交点在对方几何体上 │ │ 距离2从交点到中心 │ ▼ 中心 距离1 距离1距离2即距离1 顶点到中心的距离 ∴ 射线在到达中心之前击中了对方 → 碰撞 情况2没有碰撞射线先到中心 顶点 │ │ 距离1到中心 │ ▼ 中心 │ │ 距离2从中心到交点 │ ▼ 交点在对方几何体上 距离1 距离1距离2但射线要先经过中心 ∴ 射线先到达中心然后才可能击中对方 → 没有碰撞四、性能优化考虑1. 顶点采样优化// 不检测所有顶点只采样一部分constsampleRate0.3// 采样率30%for(leti0;iworldVertices.length;iMath.floor(1/sampleRate)){// 只检测部分顶点}2. 分层次检测// 第一步快速检测包围盒constbox1newTHREE.Box3().setFromObject(mesh1)constbox2newTHREE.Box3().setFromObject(mesh2)if(!box1.intersectsBox(box2)){returnfalse// 包围盒不相交肯定不碰撞快速返回}// 第二步精确检测顶点射线// 只有包围盒相交时才进行更耗时的顶点检测3. 空间分割优化// 使用八叉树或BVH包围盒层次结构加速// 只检测可能相交的几何体4. 距离阈值优化// 添加容差阈值constthreshold0.01// 1厘米容差if(distanceToIntersectdistanceToCenterthreshold){// 考虑为碰撞避免浮点数精度问题}五、适用场景与限制适用场景需要精确碰撞检测如物理模拟、游戏碰撞不规则几何体碰撞非AABB/球体等简单形状需要知道碰撞位置不仅仅是是否碰撞限制计算量大顶点越多越慢凹形几何体问题射线可能从凹处穿过而不碰撞薄物体问题对于非常薄的几何体可能漏检六、伪代码总结function 顶点射线碰撞检测(几何体A, 几何体B): // Step 1: 获取顶点 顶点数组A 获取世界坐标顶点(几何体A) 顶点数组B 获取世界坐标顶点(几何体B) // Step 2: 计算中心 中心A 计算世界中心(几何体A) 中心B 计算世界中心(几何体B) // Step 3-4: 检测A的顶点射线 for 每个顶点V in 顶点数组A: 方向 归一化(中心A - 顶点V) 射线 创建射线(顶点V, 方向) 交点 射线.检测相交(几何体B) if 交点存在: if 距离(顶点V, 交点) 距离(顶点V, 中心A): 碰撞计数器 // Step 3-4: 检测B的顶点射线 for 每个顶点V in 顶点数组B: 方向 归一化(中心B - 顶点V) 射线 创建射线(顶点V, 方向) 交点 射线.检测相交(几何体A) if 交点存在: if 距离(顶点V, 交点) 距离(顶点V, 中心B): 碰撞计数器 // Step 5: 判断结果 if 碰撞计数器 0: return true // 发生碰撞 else: return false // 没有碰撞—# Three.js 顶点射线碰撞检测实现步骤详解一、基本思路核心算法流程第1步遍历几何体所有顶点分别创建与几何体中心坐标构成的射线对于 每个几何体A 的 每个顶点V 顶点位置 V 的世界坐标位置 中心位置 几何体A 的世界坐标中心点 方向向量 中心位置 - 顶点位置 创建射线Raycaster 起点 顶点位置 方向 方向向量归一化实现细节获取几何体的顶点坐标数组Float32Array每3个值表示一个顶点的x,y,z坐标将顶点从局部坐标系转换到世界坐标系计算几何体的世界中心点通常是物体位置对每个顶点创建从顶点指向中心的射线第2步射线交叉计算对于 几何体A 的 每条射线R 使用Raycaster的intersectObject()方法 检测射线R 是否与 几何体B 相交 如果相交 获取交点信息交点坐标、距离、面信息等关键点Raycaster.intersectObject(几何体B) 返回交点数组交点数组按距离从近到远排序如果射线与几何体B有交点说明这条射线可能穿过了几何体B第3步通过距离判断两个网格模型是否碰撞对于 几何体A 的 每条射线R 的 每个交点I 计算起点到交点的距离 距离1 计算起点到几何体A中心的距离 距离2 如果 距离1 距离2 说明射线在到达自己中心之前就击中了对方 判断为这条射线穿过了对方几何体 如果 至少有一条射线穿过对方几何体 判断为两个几何体发生碰撞逻辑解释射线是从自己顶点指向自己中心的如果射线在到达自己中心之前就击中了对方说明对方在自己内部或前方如果射线先到达自己中心说明对方在自己后方或外部二、具体实现步骤详解Step 1: 获取几何体所有顶点世界坐标// 1.1 获取几何体的顶点位置数据constgeometrymesh.geometryconstpositionAttributegeometry.attributes.positionconstpositionspositionAttribute.array// [x1, y1, z1, x2, y2, z2, ...]// 1.2 获取世界变换矩阵constmatrixWorldmesh.matrixWorld// 1.3 遍历所有顶点转换为世界坐标constworldVertices[]for(leti0;ipositions.length;i3){// 创建局部坐标顶点constlocalVertexnewTHREE.Vector3(positions[i],// xpositions[i1],// ypositions[i2]// z)// 转换为世界坐标constworldVertexlocalVertex.clone()worldVertex.applyMatrix4(matrixWorld)worldVertices.push(worldVertex)}Step 2: 计算几何体中心点世界坐标// 方法1直接使用mesh的世界位置对于对称几何体constcenternewTHREE.Vector3()mesh.getWorldPosition(center)// 方法2计算所有顶点的平均值更精确letsumnewTHREE.Vector3(0,0,0)for(constvertexofworldVertices){sum.add(vertex)}constcentersum.divideScalar(worldVertices.length)Step 3: 创建顶点到中心的射线// 3.1 创建Raycaster实例constraycasternewTHREE.Raycaster()// 3.2 对于每个顶点创建从顶点指向中心的射线for(constvertexofworldVertices){// 计算方向向量从顶点指向中心constdirectioncenter.clone().sub(vertex).normalize()// 设置射线raycaster.set(vertex,direction)// 现在raycaster就代表了一条从顶点指向中心的射线// 我们可以用它来检测与其他几何体的交点}Step 4: 射线交叉计算// 4.1 检测射线是否与另一个几何体相交constotherMesh...// 另一个几何体constintersectsraycaster.intersectObject(otherMesh)// 4.2 分析交点信息if(intersects.length0){constfirstIntersectintersects[0]// 最近的交点// 交点信息包含// - point: 交点坐标Vector3// - distance: 从射线起点到交点的距离// - object: 被击中的物体// - face: 被击中的面// - faceIndex: 面的索引}Step 5: 通过距离判断是否碰撞// 5.1 计算关键距离constdistanceToIntersectvertex.distanceTo(firstIntersect.point)// 顶点到交点的距离constdistanceToCentervertex.distanceTo(center)// 顶点到中心的距离// 5.2 判断逻辑if(distanceToIntersectdistanceToCenter){// 情况射线在到达自己中心之前就击中了对方// 说明对方几何体在自己前方或内部// 这很可能表示两个几何体相交或碰撞console.log(这条射线穿过了对方几何体)// 记录碰撞信息collisions.push({vertex:vertex,intersectPoint:firstIntersect.point,rayDirection:direction})}else{// 情况射线先到达自己中心然后才可能击中对方// 说明对方几何体在自己后方或外部// 这很可能表示两个几何体没有碰撞}Step 6: 综合判断碰撞// 6.1 统计所有穿过的射线letcollisionCount0for(所有顶点射线){if(射线穿过了对方几何体){collisionCount}}// 6.2 判断是否发生碰撞if(collisionCount0){console.log(发生碰撞共有${collisionCount}条射线穿过对方几何体)returntrue// 发生碰撞}else{console.log(没有碰撞)returnfalse// 没有碰撞}三、数学原理图解情况1发生碰撞射线穿过对方 顶点 │ │ 距离1到交点 │ ▼ 交点在对方几何体上 │ │ 距离2从交点到中心 │ ▼ 中心 距离1 距离1距离2即距离1 顶点到中心的距离 ∴ 射线在到达中心之前击中了对方 → 碰撞 情况2没有碰撞射线先到中心 顶点 │ │ 距离1到中心 │ ▼ 中心 │ │ 距离2从中心到交点 │ ▼ 交点在对方几何体上 距离1 距离1距离2但射线要先经过中心 ∴ 射线先到达中心然后才可能击中对方 → 没有碰撞四、性能优化考虑1. 顶点采样优化// 不检测所有顶点只采样一部分constsampleRate0.3// 采样率30%for(leti0;iworldVertices.length;iMath.floor(1/sampleRate)){// 只检测部分顶点}2. 分层次检测// 第一步快速检测包围盒constbox1newTHREE.Box3().setFromObject(mesh1)constbox2newTHREE.Box3().setFromObject(mesh2)if(!box1.intersectsBox(box2)){returnfalse// 包围盒不相交肯定不碰撞快速返回}// 第二步精确检测顶点射线// 只有包围盒相交时才进行更耗时的顶点检测3. 空间分割优化// 使用八叉树或BVH包围盒层次结构加速// 只检测可能相交的几何体4. 距离阈值优化// 添加容差阈值constthreshold0.01// 1厘米容差if(distanceToIntersectdistanceToCenterthreshold){// 考虑为碰撞避免浮点数精度问题}五、适用场景与限制适用场景需要精确碰撞检测如物理模拟、游戏碰撞不规则几何体碰撞非AABB/球体等简单形状需要知道碰撞位置不仅仅是是否碰撞限制计算量大顶点越多越慢凹形几何体问题射线可能从凹处穿过而不碰撞薄物体问题对于非常薄的几何体可能漏检六、伪代码总结function 顶点射线碰撞检测(几何体A, 几何体B): // Step 1: 获取顶点 顶点数组A 获取世界坐标顶点(几何体A) 顶点数组B 获取世界坐标顶点(几何体B) // Step 2: 计算中心 中心A 计算世界中心(几何体A) 中心B 计算世界中心(几何体B) // Step 3-4: 检测A的顶点射线 for 每个顶点V in 顶点数组A: 方向 归一化(中心A - 顶点V) 射线 创建射线(顶点V, 方向) 交点 射线.检测相交(几何体B) if 交点存在: if 距离(顶点V, 交点) 距离(顶点V, 中心A): 碰撞计数器 // Step 3-4: 检测B的顶点射线 for 每个顶点V in 顶点数组B: 方向 归一化(中心B - 顶点V) 射线 创建射线(顶点V, 方向) 交点 射线.检测相交(几何体A) if 交点存在: if 距离(顶点V, 交点) 距离(顶点V, 中心B): 碰撞计数器 // Step 5: 判断结果 if 碰撞计数器 0: return true // 发生碰撞 else: return false // 没有碰撞这个算法的主要思想是如果一个几何体的顶点发出的、指向自己中心的射线在到达中心之前就击中了另一个几何体那么这两个几何体很可能发生了碰撞或相交。这个算法的主要思想是如果一个几何体的顶点发出的、指向自己中心的射线在到达中心之前就击中了另一个几何体那么这两个几何体很可能发生了碰撞或相交。templatediv refcontainerRef/div/templatescript setupimport{onMounted,ref}fromvueimport*asTHREEfromthreeconstcontainerRefref(null)letcube1,cube2onMounted((){constscenenewTHREE.Scene()scene.backgroundnewTHREE.Color(0x111111)constcameranewTHREE.PerspectiveCamera(60,window.innerWidth/window.innerHeight,0.1,1000)camera.position.set(0,5,10)constrenderernewTHREE.WebGLRenderer()renderer.setSize(window.innerWidth,window.innerHeight)containerRef.value.appendChild(renderer.domElement)// 创建立方体1可移动cube1newTHREE.Mesh(newTHREE.BoxGeometry(1,1,1),newTHREE.MeshBasicMaterial({color:0xff0000,wireframe:true}))cube1.position.set(-3,0,0)scene.add(cube1)// 创建立方体2静止cube2newTHREE.Mesh(newTHREE.BoxGeometry(1,1,1),newTHREE.MeshBasicMaterial({color:0x0000ff,wireframe:true}))cube2.position.set(0,0,0)scene.add(cube2)// 辅助线scene.add(newTHREE.AxesHelper(5))// 核心算法顶点射线碰撞检测constcheckVertexRayCollision(){console.log( 顶点射线碰撞检测 )// 1. 获取顶点世界坐标constgetVertices(mesh){constgeometrymesh.geometryconstpositionsgeometry.attributes.position.arrayconstvertices[]constmatrixWorldmesh.matrixWorldfor(leti0;ipositions.length;i3){constvertexnewTHREE.Vector3(positions[i],positions[i1],positions[i2])vertex.applyMatrix4(matrixWorld)vertices.push(vertex)}returnvertices}constvertices1getVertices(cube1)constvertices2getVertices(cube2)// 2. 获取中心点世界坐标constcenter1newTHREE.Vector3()cube1.getWorldPosition(center1)constcenter2newTHREE.Vector3()cube2.getWorldPosition(center2)console.log(立方体1顶点数:,vertices1.length)console.log(立方体2顶点数:,vertices2.length)console.log(立方体1中心:,center1)console.log(立方体2中心:,center2)// 3. 检测立方体1的顶点射线letcollisionCount0for(leti0;ivertices1.length;i){constvertexvertices1[i]constdirectionnewTHREE.Vector3().subVectors(center1,vertex).normalize()constraycasternewTHREE.Raycaster()raycaster.set(vertex,direction)constintersectsraycaster.intersectObject(cube2)if(intersects.length0){constintersectintersects[0]constdistanceToHitvertex.distanceTo(intersect.point)constdistanceToCentervertex.distanceTo(center1)if(distanceToHitdistanceToCenter){collisionCountconsole.log(立方体1顶点${i}: 射线穿过了立方体2)}}}// 4. 检测立方体2的顶点射线for(leti0;ivertices2.length;i){constvertexvertices2[i]constdirectionnewTHREE.Vector3().subVectors(center2,vertex).normalize()constraycasternewTHREE.Raycaster()raycaster.set(vertex,direction)constintersectsraycaster.intersectObject(cube1)if(intersects.length0){constintersectintersects[0]constdistanceToHitvertex.distanceTo(intersect.point)constdistanceToCentervertex.distanceTo(center2)if(distanceToHitdistanceToCenter){collisionCountconsole.log(立方体2顶点${i}: 射线穿过了立方体1)}}}// 5. 判断结果if(collisionCount0){console.log( 发生碰撞共有${collisionCount}条射线相交)cube1.material.color.set(0xff00ff)cube2.material.color.set(0xff00ff)returntrue}else{console.log(✅ 没有碰撞)cube1.material.color.set(0xff0000)cube2.material.color.set(0x0000ff)returnfalse}}// 自动移动并检测letdirection1constanimate(){requestAnimationFrame(animate)// 移动立方体1cube1.position.x0.01*direction// 边界检测if(cube1.position.x2)direction-1if(cube1.position.x-4)direction1// 每10帧检测一次if(Math.floor(cube1.position.x*10)%100){checkVertexRayCollision()}renderer.render(scene,camera)}animate()})/scriptstyle scopeddiv{width:100vw;height:100vh;}/style